簡體   English   中英

帶有 UIViewRepresentable 的 SwiftUI 自定義文本字段 ObservableObject 和推送視圖的問題

[英]SwiftUI Custom TextField with UIViewRepresentable Issue with ObservableObject and pushed View

我創建了一個UIViewRepresentable來為 SwiftUI 包裝UITextField ,因此我可以在用戶點擊回車鍵時更改第一響應者。

這是我的 UIViewRepresentable(為了簡單起見,我刪除了第一響應者代碼)

struct CustomUIKitTextField: UIViewRepresentable {

    @Binding var text: String
    var placeholder: String

    func makeUIView(context: UIViewRepresentableContext<CustomUIKitTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        textField.placeholder = placeholder
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomUIKitTextField>) {
        uiView.text = text
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiView.setContentCompressionResistancePriority(.required, for: .vertical)
    }

    func makeCoordinator() -> CustomUIKitTextField.Coordinator {
        Coordinator(parent: self)
    }

    class Coordinator: NSObject, UITextFieldDelegate {
        var parent: CustomUIKitTextField

        init(parent: CustomUIKitTextField) {
            self.parent = parent
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            parent.text = textField.text ?? ""
        }        

    }
}

應用程序的第一個屏幕有一個“使用電子郵件登錄”按鈕,該按鈕會推送顯示 CustomUIKitTextField 的CustomUIKitTextField ,並使用ObservableObject視圖模型的 @Published 屬性作為 TextField 的文本。

struct MailView: View {

    @ObservedObject var viewModel: MailSignUpViewModel

    var body: some View {
        VStack {
            CustomUIKitTextField(placeholder: self.viewModel.placeholder,
                             text: self.$viewModel.mailAddress)
                .padding(.top, 30)
                .padding(.bottom, 10)

            NavigationLink(destination: UsernameView(viewModel: UsernameSignUpViewModel())) {
                Text("Next")
            }

            Spacer()            
        }
    }
}

一切正常,直到我推送另一個視圖,如 MailView,例如 UsernameView。 它以完全相同的方式實現,但不知何故,一旦我完成輸入, CustomUIKitTextField就會以空字符串獲取updateUIView調用。

還有一些奇怪的行為,例如當我將 MailView 和 UsernameView 包裝在另一個 NavigationView 中時,一切正常。 但這顯然不是解決問題的方法,因為那時我會有多個 NavigationView。

它也適用於在視圖模型中使用 @State 屬性而不是 @Published 屬性。 但我不想使用@State,因為我真的想將模型代碼保留在視圖之外。

有沒有人遇到相同或類似的問題?

看起來您使用了錯誤的委托方法。 textFieldDidChangeSelection會產生一些不一致的結果(這聽起來像是你正在處理的)。 相反,我建議使用textFieldDidEndEditing ,它還可以讓您訪問傳入的控件,但它保證您在退出第一響應者時獲得對象。 這很重要,因為這意味着您在屬性更改后獲取對象並且它正在釋放響應者對象。

所以,我會改變你的委托方法如下:


func textFieldDidEndEditing(_ textField: UITextField) {
    parent.text = textField.text ?? ""
}

有關詳細信息,請參閱textFieldDidEndEditing方法的此鏈接: https ://developer.apple.com/documentation/uikit/uitextfielddelegate/1619591-textfielddidendediting

此鏈接可獲取有關UITextFieldDelegate對象的信息: https ://developer.apple.com/documentation/uikit/uitextfielddelegate

編輯

根據評論,如果您希望在每次更改一個字符時檢查文本,您應該實現這個委托函數:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

     // User pressed the delete key
     if string.isEmpty {
        // If you want to update your query string here after the delete key is pressed, you can rebuild it like we are below
        return true
     }

     //this will build the full string the user intends so we can use it to build our search
     let currentText = textField.text ?? ""
     let replacementText = (currentText as NSString).replacingCharacters(in: range, with: string)
     // You can set your parent.text here if you like, or you can fire the search function as well

     // When you're done though, return true to indicate that the textField should display the changes from the user

     return true
}

我還需要 SwiftUI 中的UITextField表示,它會對每個字符的變化做出反應,所以我選擇了binaryPilot84的答案。 雖然UITextFieldDelegate方法textField(_:shouldChangeCharactersIn:replacementString:)很棒,但它有一個警告——每次我們用這個方法更新文本時,光標都會移動到文本的末尾 這可能是期望的行為。 但是,它不適合我,所以我像這樣實現了目標操作:

public func makeUIView(context: Context) -> UITextField {
    let textField = UITextField()
    
    textField.addTarget(context.coordinator, action: #selector(context.coordinator.textChanged), for: .editingChanged)
    
    return textField
}


public final class Coordinator: NSObject {
    @Binding private var text: String
    
    public init(text: Binding<String>) {
        self._text = text
    }
    
    @objc func textChanged(_ sender: UITextField) {
        guard let text = sender.text else { return }
        self.text = text
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM