首页 > 解决方案 > SwiftUI 模型绑定仅在第一次通过时更新值

问题描述

我正在尝试使用 SwiftUI 创建一个表单框架。现在,我正在保存我的 FormFieldModels 的字典,以保存有关该字段是否需要、它是什么类型的字段(文本、复选框、电话等)及其值的信息。我对 SwiftUI 的经验并不丰富,但我正在尝试将字段绑定到自定义文本字段视图/复选框(最终将是更多字段选项),并从那里使用包含在字段中的值来确定状态。

由于我不完全理解的原因,只有任何字段(无论是文本字段还是切换按钮)的第一次更新有效,然后其余字段不更新主 ContentView 的状态(尽管他们的新值被正确传递,只是没有坚持)。

编码:

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
            && lhs.type == rhs.type
            && 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 或 Binding 的一些内容,这导致了混乱。谢谢

标签: iosswiftswiftuiswift5swiftui-state

解决方案


推荐阅读