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

SwiftUI TextEditor如何获取光标或指定字符的像素位置?

SwiftUI TextEditor 获取光标/特定字符位置的实现方案

一、用Introspect实现

SwiftUI的TextEditor底层基于UIKit的UITextView,Introspect可以直接获取到这个底层实例,进而计算光标或特定字符的位置:

  • 导入Introspect后,通过.introspectTextView拿到UITextView实例
  • 获取光标位置:调用caretRect(for: textView.selectedRange)拿到光标所在的矩形区域,再通过textView.convert(rect, to: nil)转成全局坐标,或转成父视图的本地坐标
  • 获取特定字符位置:先在字符串中定位目标字符(比如\()的NSRange,再用textView.firstRect(for: range)获取该字符的位置矩形
  • 注意:你设置的字号、行间距会同步到底层UITextView,所以计算出的位置是准确匹配当前排版的

示例代码:

import SwiftUI
import Introspect

struct LivePaper: View {
    @State var liveText: String = "New Document"
    @State private var caretFrame: CGRect = .zero
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            TextEditor(text: $liveText)
                .frame(width: 255, height: 330)
                .introspectTextView { textView in
                    textView.delegate = self
                    updateCaretFrame(textView: textView)
                }
            
            // 叠加在光标上方的LaTeX预览视图
            if !caretFrame.isEmpty {
                Text("LaTeX Preview")
                    .background(Color.white)
                    .offset(x: caretFrame.minX, y: caretFrame.minY - 30)
            }
        }
    }
    
    private func updateCaretFrame(textView: UITextView) {
        let rect = textView.caretRect(for: textView.selectedRange)
        caretFrame = textView.convert(rect, to: textView.superview)
    }
}

extension LivePaper: UITextViewDelegate {
    func textViewDidChangeSelection(_ textView: UITextView) {
        updateCaretFrame(textView: textView)
    }
}

二、适用的富文本库

如果需要更完善的富文本编辑能力,同时自带位置获取API,可以考虑:

  • RichTextKit:专为SwiftUI打造的富文本编辑库,支持自定义字号、行间距,能直接获取光标或特定文本块的位置信息
  • CodeEditor:针对代码/标记语言优化的编辑组件,内置光标位置监听,适配Markdown+LaTeX的编辑场景
  • 这类库已经封装了底层UITextView的逻辑,无需自己处理坐标转换,直接通过绑定或回调即可拿到位置数据

三、无第三方库的原生方案

自己通过UIViewRepresentable封装UITextView,完全掌控底层逻辑:

  1. 创建遵循UIViewRepresentable的结构体,封装UITextView
  2. 实现UITextViewDelegate,监听文本选择变化与内容变化
  3. 通过@Binding将光标/特定字符的位置传递给SwiftUI父视图
  4. 自定义字号、行间距直接设置给UITextView的font和paragraphStyle属性

示例代码:

import SwiftUI

struct CustomTextView: UIViewRepresentable {
    @Binding var text: String
    @Binding var caretFrame: CGRect
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        // 设置自定义字号和行间距
        textView.font = UIFont.systemFont(ofSize: 16)
        let style = NSMutableParagraphStyle()
        style.lineSpacing = 8
        textView.typingAttributes[.paragraphStyle] = style
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        if uiView.text != text {
            uiView.text = text
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        let parent: CustomTextView
        
        init(parent: CustomTextView) {
            self.parent = parent
        }
        
        func textViewDidChangeSelection(_ textView: UITextView) {
            let rect = textView.caretRect(for: textView.selectedRange)
            parent.caretFrame = textView.convert(rect, to: textView.superview)
            
            // 查找特定字符比如"\("的位置
            if let range = textView.text.range(of: "\\(") {
                let nsRange = NSRange(range, in: textView.text)
                let charRect = textView.firstRect(for: nsRange)
                // 可将charRect赋值给其他Binding变量用于LaTeX渲染定位
            }
        }
    }
}

// 使用示例
struct LivePaper: View {
    @State var liveText: String = "New Document"
    @State private var caretFrame: CGRect = .zero
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            CustomTextView(text: $liveText, caretFrame: $caretFrame)
                .frame(width: 255, height: 330)
            
            if !caretFrame.isEmpty {
                Text("LaTeX Preview")
                    .background(Color.white)
                    .offset(x: caretFrame.minX, y: caretFrame.minY - 30)
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 11:27:23