SwiftUI中键盘弹出时如何让TextEditor自动向上滚动文本?
解决SwiftUI中TextEditor光标被键盘遮挡的自动滚动问题
你的核心需求是让TextEditor在光标接近键盘时自动向上滚动,确保输入区域可见。当前代码仅通过添加底部padding适配键盘高度,但未处理光标位置的动态滚动,因此会出现遮挡问题。以下是两种可行的解决方案:
方案一:自定义UITextView包装(无第三方依赖)
通过UIViewRepresentable原生包装UITextView,利用其Delegate监听光标位置,结合键盘状态动态调整滚动位置。
1. 更新键盘监听类
保存完整的键盘Frame(而非仅高度),方便后续位置计算:
class KeyboardResponder: ObservableObject { @Published var keyboardFrame: CGRect = .zero init() { let center = NotificationCenter.default center.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) center.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) } @objc private func keyboardWillShow(_ notification: Notification) { guard let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } keyboardFrame = frame } @objc private func keyboardWillHide(_ notification: Notification) { keyboardFrame = .zero } }
2. 自定义WrappedTextView
包装UITextView并实现光标位置检查逻辑:
struct WrappedTextView: UIViewRepresentable { @Binding var text: String @ObservedObject var keyboardResponder: KeyboardResponder func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.delegate = context.coordinator textView.font = .systemFont(ofSize: 16) textView.backgroundColor = .clear return textView } func updateUIView(_ uiView: UITextView, context: Context) { if uiView.text != text { uiView.text = text } // 键盘状态变化时检查光标位置 if !keyboardResponder.keyboardFrame.isEmpty { context.coordinator.adjustScrollForCursor(in: uiView) } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } class Coordinator: NSObject, UITextViewDelegate { let parent: WrappedTextView init(parent: WrappedTextView) { self.parent = parent } func textViewDidChange(_ textView: UITextView) { parent.text = textView.text // 每次输入后检查光标位置 adjustScrollForCursor(in: textView) } func adjustScrollForCursor(in textView: UITextView) { guard let selectedRange = textView.selectedTextRange else { return } let cursorRect = textView.caretRect(for: selectedRange.end) guard let window = textView.window else { return } let cursorWindowRect = textView.convert(cursorRect, to: window) // 计算键盘顶部的Y坐标 let keyboardTopY = window.bounds.height - parent.keyboardResponder.keyboardFrame.height // 光标底部超过键盘顶部时,调整滚动偏移 if cursorWindowRect.maxY > keyboardTopY { let offsetY = cursorWindowRect.maxY - keyboardTopY + 8 // 增加8pt间距 textView.contentOffset.y += offsetY } } } }
3. 使用自定义TextView
替换原有的TextEditor,去掉外层ScrollView(避免滚动冲突):
struct NewEntryView: View { @ObservedObject private var keyboardResponder = KeyboardResponder() @State private var text: String = "" var body: some View { WrappedTextView(text: $text, keyboardResponder: keyboardResponder) .padding() .padding(.bottom, keyboardResponder.keyboardFrame.height) .animation(.easeOut(duration: 0.3), value: keyboardResponder.keyboardFrame.height) .background(Color(UIColor.systemBackground)) .edgesIgnoringSafeArea(.bottom) } }
方案二:使用Introspect库简化实现(依赖第三方)
如果不想自定义TextView,可以用Introspect库直接获取TextEditor底层的UITextView,代码更简洁:
1. 添加Introspect依赖
通过Swift Package Manager添加:https://github.com/siteline/SwiftUI-Introspect
2. 实现滚动调整逻辑
import Introspect struct NewEntryView: View { @ObservedObject private var keyboardResponder = KeyboardResponder() @State private var text: String = "" @State private var textView: UITextView? var body: some View { TextEditor(text: $text) .padding() .padding(.bottom, keyboardResponder.keyboardFrame.height) .animation(.easeOut(duration: 0.3), value: keyboardResponder.keyboardFrame.height) // 绑定底层UITextView .introspectTextView { tv in textView = tv tv.delegate = self } // 文本或键盘状态变化时调整滚动 .onChange(of: text) { _ in adjustScrollForCursor() } .onChange(of: keyboardResponder.keyboardFrame) { _ in adjustScrollForCursor() } } private func adjustScrollForCursor() { guard let textView = textView, let selectedRange = textView.selectedTextRange else { return } let cursorRect = textView.caretRect(for: selectedRange.end) guard let window = textView.window else { return } let cursorWindowRect = textView.convert(cursorRect, to: window) let keyboardTopY = window.bounds.height - keyboardResponder.keyboardFrame.height if cursorWindowRect.maxY > keyboardTopY { let offsetY = cursorWindowRect.maxY - keyboardTopY + 8 textView.contentOffset.y += offsetY } } } extension NewEntryView: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { adjustScrollForCursor() } }
方案说明
两种方案核心逻辑一致:
- 监听键盘的显示/隐藏事件,获取键盘位置
- 监听文本输入和光标变化,计算光标在窗口坐标系中的位置
- 当光标底部超过键盘顶部时,调整TextEditor的滚动偏移,确保光标可见
方案一无第三方依赖,更稳定;方案二代码更简洁,但需要引入外部库,可根据项目需求选择。
内容的提问来源于stack exchange,提问作者Nenharmha
相关产品推荐
相关产品推荐

