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,完全掌控底层逻辑:
- 创建遵循
UIViewRepresentable的结构体,封装UITextView - 实现
UITextViewDelegate,监听文本选择变化与内容变化 - 通过
@Binding将光标/特定字符的位置传递给SwiftUI父视图 - 自定义字号、行间距直接设置给
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
相关产品推荐
相关产品推荐

