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
相关产品推荐
相关产品推荐

