SwiftUI UIViewRepresentable UITextField在输入表情符号时设置光标位置

时间:2019-11-17 11:21:41

标签: uikit swiftui

我正在使用包装在UIViewRepresentable中的UITextField,因此可以在SwiftUI中使用它。我从几个SO答案中总结了这一点。

struct RADTextField: UIViewRepresentable {

    @ObservedObject var dataVM: DataVM
    var placeholder: String
    var isFirstResponder: Bool = false

    func makeUIView(context: UIViewRepresentableContext<RADTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
        textField.delegate = context.coordinator
        textField.clearButtonMode = .whileEditing
        textField.returnKeyType = .done
        textField.placeholder = self.placeholder
        textField.adjustsFontForContentSizeCategory = true

        return textField
    }

    func makeCoordinator() -> RADTextField.Coordinator {
        return Coordinator(dataVM: self.dataVM)
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<RADTextField>) {

        uiView.text = dataVM.fieldOne
        if isFirstResponder && !context.coordinator.didBecomeFirstResponder  {
            uiView.becomeFirstResponder()
            context.coordinator.didBecomeFirstResponder = true
        }
    }

}

协调员:

extension RADTextField {
    class Coordinator: NSObject, UITextFieldDelegate {

        @ObservedObject var dataVM : DataVM
        var didBecomeFirstResponder = false

        init(dataVM: DataVM) {
            self.dataVM = dataVM
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            self.dataVM.fieldOne = textField.text ?? ""
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            textField.resignFirstResponder()
        }

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            // Update Cursor
            let positionOriginal = textField.beginningOfDocument

            let cursorLocation = textField.position(from: positionOriginal, offset: (range.location + string.count))

            if let txt = textField.text {
                textField.text = NSString(string: txt).replacingCharacters(in: range, with: string)
            }
            // ✳️ I think this is where the issue is ✳️
            if let cursorLocation = cursorLocation {
                textField.selectedTextRange = textField.textRange(from: cursorLocation, to: cursorLocation)
            }

            return false
        }
    }
}

问题是,当我尝试将光标手动放置在文本中的某个位置并键入表情符号时,我键入的下一个字符将“分割”表情符号。我认为某些表情符号字符实际上是两个字符,但是我不知道该如何处理。我用the️标记了代码,指出了我认为问题出在哪里。

1 个答案:

答案 0 :(得分:0)

如果 UITextField 文本没有在每次输入字符时使用模型中的值进行不必要的更新,则可以避免设置光标位置的需要。

这可以通过将 textField 回调方法中接收到的当前文本存储在 Coordinator 的一个属性中来实现。在 updateUIView 方法中,此属性用于与模型值进行比较。

这是一个简化的例子:

struct WrappedUITextField: UIViewRepresentable {
    @Binding var text: String
            
    func makeCoordinator() -> Coordinator {
        Coordinator($text)
    }
    
    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        textField.text = text
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        if text != context.coordinator.currentText {
            uiView.text = text
        }
    }
}

class Coordinator: NSObject, UITextFieldDelegate {
    var text: Binding<String>
    var currentText: String
    
    init(_ text: Binding<String>) {
        self.text = text
        currentText = text.wrappedValue
    }
        
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let oldText = textField.text, let textRange = Range(range, in: oldText) else {
            return false
        }

        currentText = oldText.replacingCharacters(in: textRange, with: string)
        text.wrappedValue = currentText
        
        return true
    }
}

工作的双向绑定可以测试如下:

struct ContentView: View {
    @State var text = "example"
    
    var body: some View {
        WrappedUITextField(text: $text)
        WrappedUITextField(text: $text)
    }
}