如何获取 SwiftUI 中 TextEditor 的光标位置?

Imr*_*ain 8 ios swift swiftui

因此,在我的文本编辑器中,我想知道光标的几何位置。我还计划在该位置之后附加一些文字。

那么我该怎么做呢?

KAN*_* UG 1

好吧...所以我想出了一个方法来做到这一点。

首先,我创建了一个struct来存储光标位置

import foundation

struct CursorPosition {
    start: Int
    end: Int
}
Run Code Online (Sandbox Code Playgroud)

然后我将其初始化为静态

class Global {
    public static var cursorPosition = CursorPosition(start: 0, end: 0)
}
Run Code Online (Sandbox Code Playgroud)

最后,我创建了一个自定义视图来匹配 SwiftUITextEditor并监听选择更改并更新CursorPosition

import UIKit
import SwiftUI

fileprivate struct UITextViewWrapper: UIViewRepresentable {
    typealias UIViewType = UITextView

    @Binding var text: String
    var onDone: (() -> Void)?

    func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
        let textField = UITextView()
        textField.delegate = context.coordinator

        textField.isEditable = true
        textField.font = UIFont.preferredFont(forTextStyle: .body)
        textField.isSelectable = true
        textField.isUserInteractionEnabled = true
        textField.isScrollEnabled = true
        textField.backgroundColor = UIColor.clear
        if nil != onDone {
            textField.returnKeyType = .done
        }

        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        return textField
    }

    func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
        if uiView.text != self.text {
            uiView.text = self.text
        }
        if uiView.window != nil, !uiView.isFirstResponder {
            uiView.becomeFirstResponder()
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(text: $text, onDone: onDone)
    }

    final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var onDone: (() -> Void)?

        init(text: Binding<String>, onDone: (() -> Void)? = nil) {
            self.text = text
            self.onDone = onDone
        }

        func textViewDidChange(_ uiView: UITextView) {
            text.wrappedValue = uiView.text
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if let onDone = self.onDone, text == "\n" {
                textView.resignFirstResponder()
                onDone()
                return false
            }
            return true
        }
        
        func textViewDidChangeSelection(_ textView: UITextView) {
            if let range = textView.selectedTextRange {
                Global.cursorPosition.start = textView.offset(from: textView.beginningOfDocument, to: range.start)
                Global.cursorPosition.end = textView.offset(from: textView.beginningOfDocument, to: range.end)
            }
        }
        
    }

}

struct EditText: View {

    private var placeholder: String
    private var onCommit: (() -> Void)?

    @Binding private var text: String
    private var internalText: Binding<String> {
        Binding<String>(get: { self.text } ) {
            self.text = $0
            self.showingPlaceholder = $0.isEmpty
        }
    }

    @State private var showingPlaceholder = false

    init (_ placeholder: String = "", text: Binding<String>, onCommit: (() -> Void)? = nil) {
        self.placeholder = placeholder
        self.onCommit = onCommit
        self._text = text
        self._showingPlaceholder = State<Bool>(initialValue: self.text.isEmpty)
    }

    var body: some View {
        UITextViewWrapper(text: self.internalText, onDone: onCommit)
            .background(placeholderView, alignment: .topLeading)
    }

    var placeholderView: some View {
        Group {
            if showingPlaceholder {
                Text(placeholder).foregroundColor(.gray)
                    .padding(.leading, 4)
                    .padding(.top, 8)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用它:

EditText("", text: $text)
    .onChange(of: text){ _ in
        let cursorStart = Global.cursorPosition.start
    }
Run Code Online (Sandbox Code Playgroud)