You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI 中如何实现滚动到 TextEditor 的当前光标位置?

问题根源

你当前的实现中每次文本内容变更都会强制滚动到TextEditor的底部锚点,因此只要编辑内容,无论光标在什么位置,都会立刻跳转到TextEditor底部,自然无法正常查看上方的文本内容。

解决方案

方案一:简化实现(适配绝大多数场景)

你原本的核心需求是保证TextEditor不被键盘遮挡,不需要每次输入都触发滚动,只需要监听键盘弹出事件,仅在键盘弹出的那一刻滚动一次即可,日常编辑过程中不会强制跳转,可正常查看上下部分的内容。
实现代码如下:

import SwiftUI
import Combine

struct ContentView: View {
    @State var items: [String] = ["Apples", "Oranges", "Bananas", "Pears", "Mangos", "Grapefruit","Apples", "Oranges", "Bananas", "Pears", "Mangos", "Grapefruit"]
    @State private var newItemText : String = ""
    @State private var keyboardCancellable: AnyCancellable?

    var body: some View {
        ScrollViewReader { proxy in
            List{
                ForEach(items, id: \.self) {
                    Text("\($0)")
                }
                TextEditor(text: $newItemText)
                    .id("New Item TextField")
            }
            .onAppear {
                // 仅监听键盘弹出事件,触发一次滚动
                keyboardCancellable = NotificationCenter.default.publisher(
                    for: UIResponder.keyboardWillShowNotification
                )
                .sink { _ in
                    withAnimation {
                        proxy.scrollTo("New Item TextField", anchor: .bottom)
                    }
                }
            }
            .onDisappear {
                keyboardCancellable?.cancel()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

方案二:滚动到当前光标位置(进阶需求)

如果确实需要每次编辑时自动滚动到光标所在位置,可以借助UIKit的UITextView获取光标坐标,计算对应锚点后触发滚动,实现代码如下:

import SwiftUI
import Combine

// 封装可感知光标位置的TextEditor
struct CursorTrackableTextEditor: UIViewRepresentable {
    @Binding var text: String
    var onCursorUpdate: (CGRect, CGFloat) -> Void

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = .preferredFont(forTextStyle: .body)
        textView.text = text
        return textView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UITextViewDelegate {
        let parent: CursorTrackableTextEditor
        init(_ parent: CursorTrackableTextEditor) {
            self.parent = parent
        }

        func textViewDidChange(_ textView: UITextView) {
            parent.text = textView.text
            guard let range = textView.selectedTextRange else { return }
            let cursorRect = textView.caretRect(for: range.start)
            parent.onCursorUpdate(cursorRect, textView.contentSize.height)
        }
    }
}

struct ContentView: View {
    @State var items: [String] = ["Apples", "Oranges", "Bananas", "Pears", "Mangos", "Grapefruit","Apples", "Oranges", "Bananas", "Pears", "Mangos", "Grapefruit"]
    @State private var newItemText : String = ""
    @State private var keyboardCancellable: AnyCancellable?
    private let editorId = "New Item TextField"

    var body: some View {
        ScrollViewReader { proxy in
            List{
                ForEach(items, id: \.self) {
                    Text("\($0)")
                }
                CursorTrackableTextEditor(text: $newItemText) { cursorRect, contentHeight in
                    guard contentHeight > 0 else { return }
                    // 计算光标对应的垂直锚点比例
                    let anchorY = cursorRect.midY / contentHeight
                    withAnimation {
                        proxy.scrollTo(editorId, anchor: UnitPoint(x: 0.5, y: anchorY))
                    }
                }
                .id(editorId)
            }
            .onAppear {
                keyboardCancellable = NotificationCenter.default.publisher(
                    for: UIResponder.keyboardWillShowNotification
                )
                .sink { _ in
                    withAnimation {
                        proxy.scrollTo(editorId, anchor: .bottom)
                    }
                }
            }
            .onDisappear {
                keyboardCancellable?.cancel()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

内容的提问来源于stack exchange,提问作者unknown

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 11:54:03