SwiftUI TextEditor光标位置插入项目符号失效问题排查
SwiftUI TextEditor 光标位置插入项目符号及自动换行优化方案
问题根源
你遇到的核心问题是**cursorPosition变量从未被正确赋值**——SwiftUI原生TextEditor不直接暴露光标位置,导致你的插入逻辑一直走默认分支,把项目符号加到文本末尾。此外,原自动换行添加项目符号的逻辑存在判断不准确、触发循环更新的问题。
解决方案
通过自定义基于UIKitUITextView的包装组件,监听光标位置并处理自动换行逻辑,完美解决这两个问题:
1. 自定义带光标监听的TextEditor
这个组件会实时同步光标位置,并在用户按下回车时自动判断是否添加项目符号:
struct CursorTextEditor: UIViewRepresentable { @Binding var text: String @Binding var cursorPosition: Int? func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.delegate = context.coordinator textView.font = UIFont.systemFont(ofSize: 20) return textView } func updateUIView(_ uiView: UITextView, context: Context) { if uiView.text != text { uiView.text = text } // 文本更新后保持光标位置,避免跳转到末尾 if let position = cursorPosition { guard let start = uiView.position(from: uiView.beginningOfDocument, offset: position) else { return } uiView.selectedTextRange = uiView.textRange(from: start, to: start) } } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITextViewDelegate { let parent: CursorTextEditor init(_ parent: CursorTextEditor) { self.parent = parent } // 实时监听光标位置变化 func textViewDidChangeSelection(_ textView: UITextView) { let position = textView.offset(from: textView.beginningOfDocument, to: textView.selectedTextRange?.start ?? textView.beginningOfDocument) parent.cursorPosition = position } // 处理回车自动添加项目符号 func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard text == "\n" else { return true } // 获取当前行文本 let lineStart = textView.position(from: textView.beginningOfDocument, offset: range.location) let lineRange = textView.textRange(from: lineStart ?? textView.beginningOfDocument, to: textView.endOfDocument) let lineText = textView.text(in: lineRange ?? textView.textRange(from: textView.beginningOfDocument, to: textView.endOfDocument)!) // 仅当当前行以项目符号开头时,自动添加新的项目符号 if lineText?.trimmingCharacters(in: .whitespaces).hasPrefix("• ") == true { textView.insertText("\n• ") return false // 阻止默认换行,避免重复添加 } return true } } }
2. 重构EditorField组件
替换原生TextEditor为自定义组件,优化插入项目符号的逻辑:
struct EditorField: View { @Binding var text: String @State private var cursorPosition: Int? var placeholder: String var height: Int public var body: some View { VStack { Button(action: insertBullet) { Text("添加项目符号") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } Spacer() ZStack(alignment: .topLeading) { CursorTextEditor(text: $text, cursorPosition: $cursorPosition) .foregroundColor(.black) .frame(width: .infinity, height: CGFloat(height)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.blue, lineWidth: 3) ) .padding(10) // 占位符,允许点击穿透到文本编辑器 if text.isEmpty { Text(placeholder) .font(.title3) .foregroundColor(.gray) .padding(20) .opacity(0.5) .allowsHitTesting(false) } } } } private func insertBullet() { guard let cursorPos = cursorPosition else { text += "\n• " return } // 确保光标位置不超出文本长度 let safePos = min(cursorPos, text.count) let index = text.index(text.startIndex, offsetBy: safePos) text.insert(contentsOf: "• ", at: index) // 插入后将光标移动到项目符号后方 cursorPosition = safePos + 2 } }
核心优化点
- 光标位置同步:通过UITextView的代理方法实时获取光标位置,确保插入操作精准定位
- 自动换行逻辑:直接在文本编辑代理中处理回车事件,判断当前行是否以项目符号开头,避免原逻辑中的循环更新问题
- 用户体验优化:插入项目符号后自动将光标移动到符号后方,无需手动调整
内容的提问来源于stack exchange,提问作者Edoardo Sorgentone
相关产品推荐
相关产品推荐

