#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 или привязке здесь, что вызывает путаницу. Спасибо