Невозможно обновить значение при переносе привязки SwiftUI в другую привязку - PullRequest
1 голос
/ 23 марта 2020

Я хочу сделать экран с кодом входа. Он состоит из 4 отдельных UITextField элементов, каждый из которых принимает один символ. Я реализовал систему, в которой каждый раз, когда одно из изменений UITextField проверяет, все ли значения заполнены, и обновляют ли они логическую привязку, чтобы сообщить родительскому объекту, что код верен.

Чтобы сделать это, я обертываю переменные @State в пользовательскую привязку, которая выполняет обратный вызов в установщике, например:

@State private var chars:[String] = ["","","",""]

...
var body: some View {
        var bindings:[Binding<String>] = []

        for x in 0..<self.chars.count {
            let b = Binding<String>(get: {
                return self.chars[x]
            }, set: {
                self.chars[x] = $0
                self.validateCode()
            })

            bindings.append(b)
        }

, и эти привязки передаются компонентам. Каждый раз, когда мое текстовое значение изменяется validateCode вызывается. Это прекрасно работает.

Однако теперь я хочу добавить дополнительное поведение: если пользователь вводит 4 символа, а код неправильный, я хочу переместить первого респондента обратно в первое текстовое поле и очистить его содержимое. Первая часть респондента работает нормально (я также управляю этим с помощью переменных @State, но я не использую для них связывающую обертку), однако я не могу изменить текст внутри своего кода. Я думаю, это потому, что мои компоненты используют эту обернутую привязку, а не переменную, содержащую текст.

Вот как выглядит мой validateCode:

    func validateCode() {
        let combinedCode = chars.reduce("") { (result, string) -> String in
            return result + string
        }

        self.isValid = value == combinedCode

        if !isValid && combinedCode.count == chars.count {

            self.hasFocus = [true,false,false,false]
            self.chars = ["","","",""]
        }
    }

hasFocus делает свое дело правильно и курсор перемещается на первый UITextField. Однако текст остается в текстовых полях. Я попытался создать эти привязки в init, чтобы я мог также использовать их в своей функции validateCode, но это дает всевозможные ошибки компиляции, потому что я использую self внутри геттера и сеттера.

Есть идеи, как это решить? Должен ли я работать с Observables? Я только начинаю со SwiftUI, так что, возможно, мне не хватает некоторых инструментов, которые я могу использовать для этого.

Для полноты, вот код всего файла:

import SwiftUI

struct CWCodeView: View {
    var value:String
    @Binding var isValid:Bool

    @State private var chars:[String] = ["","","",""]

    @State private var hasFocus = [true,false,false,false]
    @State private var nothingHasFocus:Bool = false

    init(value:String,isValid:Binding<Bool>) {
        self.value = value
        self._isValid = isValid

    }

    func validateCode() {
        let combinedCode = chars.reduce("") { (result, string) -> String in
            return result + string
        }
        self.isValid = value == combinedCode


        if !isValid && combinedCode.count == chars.count {

            self.hasFocus = [true,false,false,false]
            self.nothingHasFocus = false
            self.chars = ["","","",""]
        }
    }

    var body: some View {
        var bindings:[Binding<String>] = []

        for x in 0..<self.chars.count {
            let b = Binding<String>(get: {
                return self.chars[x]
            }, set: {
                self.chars[x] = $0
                self.validateCode()
            })

            bindings.append(b)
        }


        return GeometryReader { geometry in
            ScrollView (.vertical){
                VStack{
                    HStack {
                        CWNumberField(letter: bindings[0],hasFocus: self.$hasFocus[0], previousHasFocus: self.$nothingHasFocus, nextHasFocus: self.$hasFocus[1])
                        CWNumberField(letter: bindings[1],hasFocus: self.$hasFocus[1], previousHasFocus: self.$hasFocus[0], nextHasFocus: self.$hasFocus[2])
                        CWNumberField(letter: bindings[2],hasFocus: self.$hasFocus[2], previousHasFocus: self.$hasFocus[1], nextHasFocus: self.$hasFocus[3])
                        CWNumberField(letter: bindings[3],hasFocus: self.$hasFocus[3], previousHasFocus: self.$hasFocus[2], nextHasFocus: self.$nothingHasFocus)
                    }
                }
                .frame(width: geometry.size.width)
                .frame(height: geometry.size.height)
                .modifier(AdaptsToSoftwareKeyboard())
            }
        }
    }
}

struct CWCodeView_Previews: PreviewProvider {
    static var previews: some View {
        CWCodeView(value: "1000", isValid: .constant(false))
    }
}

struct CWNumberField : View {
    @Binding var letter:String
    @Binding var hasFocus:Bool
    @Binding var previousHasFocus:Bool
    @Binding var nextHasFocus:Bool

    var body: some View {
        CWSingleCharacterTextField(character:$letter,hasFocus: $hasFocus, previousHasFocus: $previousHasFocus, nextHasFocus: $nextHasFocus)
            .frame(width: 46,height:56)
            .keyboardType(.numberPad)
            .overlay(
                RoundedRectangle(cornerRadius: 5)
                    .stroke(Color.init("codeBorder"), lineWidth: 1)
            )

    }
}

struct CWSingleCharacterTextField : UIViewRepresentable {
    @Binding var character: String
    @Binding var hasFocus:Bool
    @Binding var previousHasFocus:Bool
    @Binding var nextHasFocus:Bool

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField.init()
        //textField.isSecureTextEntry = true
        textField.keyboardType = .numberPad
        textField.delegate = context.coordinator
        textField.textAlignment = .center
        textField.font = UIFont.systemFont(ofSize: 16)
        textField.tintColor = .black
        textField.text = character

        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        if hasFocus {
            DispatchQueue.main.async {
                uiView.becomeFirstResponder()
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }

    class Coordinator : NSObject, UITextFieldDelegate {
        var parent:CWSingleCharacterTextField

        init(_ parent:CWSingleCharacterTextField) {
            self.parent = parent
        }

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            let result = (textField.text! as NSString).replacingCharacters(in: range, with: string)

            if result.count > 0 {
                DispatchQueue.main.async{
                    self.parent.hasFocus = false
                    self.parent.nextHasFocus = true
                }
            } else {
                DispatchQueue.main.async{
                    self.parent.hasFocus = false
                    self.parent.previousHasFocus = true
                }
            }

            if result.count <= 1 {
                parent.character = string
                return true
            }

            return false
        }
    }
}

Спасибо!

1 Ответ

0 голосов
/ 23 марта 2020

вы просто совершаете небольшую ошибку, но я не могу поверить, что вы только что "запустили" SwiftUI;)

1.) Просто создайте текстовое поле один раз, поэтому я принял его как переменную-член вместо создания всегда новый 2.) обновить текст в updateuiview -> вот и все 3.) ... почти: все еще есть проблема с фокусом / обновлением ... последнее из четырех текстовых полей не будет корректно обновляться ... я предполагаю, что это проблема фокуса ....

попробуйте это:

struct CWSingleCharacterTextField : UIViewRepresentable {
    @Binding var character: String
    @Binding var hasFocus:Bool
    @Binding var previousHasFocus:Bool
    @Binding var nextHasFocus:Bool

    let textField = UITextField.init()

    func makeUIView(context: Context) -> UITextField {
        //textField.isSecureTextEntry = true
        textField.keyboardType = .numberPad
        textField.delegate = context.coordinator
        textField.textAlignment = .center
        textField.font = UIFont.systemFont(ofSize: 16)
        textField.tintColor = .black
        textField.text = character

        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {

        uiView.text = character

        if hasFocus {
            DispatchQueue.main.async {
                uiView.becomeFirstResponder()
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }

    class Coordinator : NSObject, UITextFieldDelegate {
        var parent:CWSingleCharacterTextField

        init(_ parent:CWSingleCharacterTextField) {
            self.parent = parent
        }

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            let result = (textField.text! as NSString).replacingCharacters(in: range, with: string)

            if result.count > 0 {
                DispatchQueue.main.async{
                    self.parent.hasFocus = false
                    self.parent.nextHasFocus = true
                }
            } else {
                DispatchQueue.main.async{
                    self.parent.hasFocus = false
                    self.parent.previousHasFocus = true
                }
            }

            if result.count <= 1 {
                parent.character = string
                return true
            }

            return false
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...