Привязка модели SwiftUI обновляет значение только при первом проходе

#ios #swift #swiftui #swift5 #swiftui-state

Вопрос:

Я пытаюсь создать структуру форм с помощью SwiftUI. Прямо сейчас я веду словарь своих моделей полей формы, чтобы сохранить информацию о том, требуется ли поле, какой это тип поля (текст, флажок, телефон и т. Д.) И его значение. У меня нет такого опыта работы с SwiftUI, но я пытаюсь привязать поле к пользовательскому виду текстового поля/флажку (в конечном итоге будет больше параметров поля) и оттуда использовать значение, заключенное в поле, для определения состояния.

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

Код:

 import SwiftUI

enum FormFieldType: Hashable, Equatable {
    
    case plainText
    case checkbox
    case ageGate
    case phone
    case picker([String])
        
}

struct FormFieldModel: Hashable {
    
    //Constants
    let title: String
    let type: FormFieldType
    let required: Bool
    var value: Any?
    
    func validate() -> Bool {
        switch type {
        case .phone:
            return false
        default:
            return true
        }
    }
    
    //MARK: Equatable Conformance
    static func == (lhs: FormFieldModel, rhs: FormFieldModel) -> Bool {
        return lhs.title == rhs.title
            amp;amp; lhs.type == rhs.type
            amp;amp; lhs.required == rhs.required
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
        hasher.combine(type)
        hasher.combine(required)
    }
    
}

struct AddressViewModel {
    let first:String
    let last:String
}

struct ContentView: View {
    
    enum FieldKey: String {
        case first, last, enroll
    }
    
    var existingAddress: AddressViewModel?
    @State var fields:[FieldKey:FormFieldModel] = [.first : .init(title: "First Name", type: .plainText,
                                                                  required: true, value: nil),
                                                   .last  : .init(title: "Last Name", type: .plainText,
                                                                  required: true, value: nil),
                                                   .enroll : .init(title: "Enroll", type: .checkbox,
                                                                   required: true, value: nil)]
    {
        didSet {
            print("fields has been modified")
            let v = fields[.first]?.value as? String ?? "none"
            print("new value of first is (v)")
        }
    }
    
    var body: some View {
        VStack {
            FloatingTextField(model: binding(for: .first))
            FloatingTextField(model: binding(for: .last))
            CheckboxView(model: binding(for: .enroll))
            Button("Print Values") {
                printValues()
            }
            Spacer()
        }
        .padding()
    }
    
    func printValues() {
        print("(FieldKey.first.rawValue): (fields[.first]?.value ?? "none")")
        print("(FieldKey.last.rawValue): (fields[.last]?.value ?? "none")")
        print("(FieldKey.enroll.rawValue): (fields[.enroll]?.value ?? "none")")
    }
    
    func binding(for key: FieldKey) -> Binding<FormFieldModel> {
        return Binding(get: {
            return self.fields[key]!
        }, set: {
            print("Model is being set with value ($0)")
            self.fields[key] = $0
            print("Fields is (self.fields)")
            print("-------------------------------------------------------")
        })
    }
    
}

struct FloatingTextField: View {
    
    let model: Binding<FormFieldModel>
    var text: Binding<String> {
        Binding {
            return self.model.wrappedValue.value as? String ?? ""
        } set: {
            print("Setting value to ($0)")
            self.model.wrappedValue.value = $0
        }
    }
    
    var body: some View {
        ZStack(alignment: .leading) {
            Text(model.wrappedValue.title)
                .foregroundColor(Color(.placeholderText))
                .offset(y: text.wrappedValue.isEmpty ? 0 : -25)
                .scaleEffect(text.wrappedValue.isEmpty ? 1 : 0.8, anchor: .leading)
            TextField("", text: text) // give TextField an empty placeholder
        }
        .padding(.top, 15)
        .animation(.spring(response: 0.2, dampingFraction: 0.5))
        .overlay(
            RoundedRectangle(cornerRadius: 8)
                .stroke(Color.blue, lineWidth: 1.5)
        )
    }
    
}

struct CheckboxView: View {
    
    let model: Binding<FormFieldModel>
    var selected: Binding<Bool> {
        Binding {
            return self.model.wrappedValue.value as? Bool ?? false
        } set: {
            self.model.wrappedValue.value = $0
        }
    }
    
    var body: some View {
        HStack {
            Button(selected.wrappedValue ? "selected" : "deselected") {
                selected.wrappedValue.toggle()
            }
            Text("Enroll?")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let address:AddressViewModel = .init(first: "Matt", last: "Gannon")
        ContentView(existingAddress: address)
    }
}
 

Программа печатает следующее при вводе в одно из текстовых полей. Как вы можете видеть, сохранилась только первая напечатанная буква:

 Setting value to B
Model is being set with value FormFieldModel(title: "First Name", type: FormUI.FormFieldType.plainText, required: true, value: Optional("B"))
fields has been modified
new value of first is B
Fields is [FormUI.ContentView.FieldKey.first: FormUI.FormFieldModel(title: "First Name", type: FormUI.FormFieldType.plainText, required: true, value: Optional("B")), FormUI.ContentView.FieldKey.enroll: FormUI.FormFieldModel(title: "Enroll", type: FormUI.FormFieldType.checkbox, required: true, value: nil), FormUI.ContentView.FieldKey.last: FormUI.FormFieldModel(title: "Last Name", type: FormUI.FormFieldType.plainText, required: true, value: nil)]
-------------------------------------------------------
Setting value to Bo
Model is being set with value FormFieldModel(title: "First Name", type: FormUI.FormFieldType.plainText, required: true, value: Optional("Bo"))
fields has been modified
new value of first is B
Fields is [FormUI.ContentView.FieldKey.first: FormUI.FormFieldModel(title: "First Name", type: FormUI.FormFieldType.plainText, required: true, value: Optional("B")), FormUI.ContentView.FieldKey.enroll: FormUI.FormFieldModel(title: "Enroll", type: FormUI.FormFieldType.checkbox, required: true, value: nil), FormUI.ContentView.FieldKey.last: FormUI.FormFieldModel(title: "Last Name", type: FormUI.FormFieldType.plainText, required: true, value: nil)]
-------------------------------------------------------
Setting value to Bob
Model is being set with value FormFieldModel(title: "First Name", type: FormUI.FormFieldType.plainText, required: true, value: Optional("Bob"))
fields has been modified
new value of first is B
Fields is [FormUI.ContentView.FieldKey.first: FormUI.FormFieldModel(title: "First Name", type: FormUI.FormFieldType.plainText, required: true, value: Optional("B")), FormUI.ContentView.FieldKey.enroll: FormUI.FormFieldModel(title: "Enroll", type: FormUI.FormFieldType.checkbox, required: true, value: nil), FormUI.ContentView.FieldKey.last: FormUI.FormFieldModel(title: "Last Name", type: FormUI.FormFieldType.plainText, required: true, value: nil)]
-------------------------------------------------------
 

Любые советы будут оценены по достоинству. Я уверен, что я неправильно понял что-то в @State или привязке здесь, что вызывает путаницу. Спасибо