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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 22:33:19