Reputation: 5041
I'm working on a validation routine for a form, but when the validation results come in, the onChange is not being triggered.
So I have a form that has some fields, and some nested items that have some more fields (the number of items may vary). Think of a form for creating teams where you get to add people.
When the form is submitted, it sends a message to each item to validate itself, and the results of the validation of each item are stored in an array of booleans. Once all the booleans of the array are true, the form is submitted.
Every time a change occurs in the array of results, it should change a flag that would check if all items are true, and if they are, submits the form. But whenever I change the flag, the onChange
I have for it never gets called:
final class AddEditProjectViewModel: ObservableObject {
@Published var array = ["1", "2", "3", "hello"]
// In reality this array would be a collection of objects with many properties
}
struct AddEditItemView: View {
@State var text : String
@Binding var doValidation: Bool // flag to perform the item validation
@Binding var isValid : Bool // result of validating all fields in this item
init(text: String, isValid: Binding<Bool>, doValidation: Binding<Bool>) {
self._text = State(initialValue: text)
self._isValid = isValid
self._doValidation = doValidation
}
func validateAll() {
// here would be some validation logic for all form fields,
//but I'm simulating the result to all items passed validation
// Validation needs to happen here because there are error message
//fields within the item view that get turned on or off
isValid = true
}
var body: some View {
Text(text)
.onChange(of: doValidation, perform: { value in
validateAll() // when the flag changes, perform the validation
})
}
}
struct ContentView: View {
@ObservedObject var viewModel : AddEditProjectViewModel
@State var performValidateItems : Bool = false // flag to perform the validation of all items
@State var submitFormFlag = false // flag to detect when validation results come in
@State var itemsValidationResult = [Bool]() // store the validation results of each item
{
didSet {
print(submitFormFlag) // i.e. false
submitFormFlag.toggle() // Even though this gets changed, on changed on it won't get called
print(submitFormFlag) // i.e. true
}
}
init(viewModel : AddEditProjectViewModel) {
self.viewModel = viewModel
var initialValues = [Bool]()
for _ in (0..<viewModel.array.count) { // populate the initial validation results all to false
initialValues.append(false)
}
_itemsValidationResult = State(initialValue: initialValues)
}
//https://stackoverflow.com/questions/56978746/how-do-i-bind-a-swiftui-element-to-a-value-in-a-dictionary
func binding(for index: Int) -> Binding<Bool> {
return Binding(get: {
return self.itemsValidationResult[index]
}, set: {
self.itemsValidationResult[index] = $0
})
}
var body: some View {
HStack {
ForEach(viewModel.array.indices, id: \.self) { i in
AddEditItemView(
text: viewModel.array[i],
isValid: binding(for: i),
doValidation: $performValidateItems
)
}
Text(itemsValidationResult.description)
Button(action: {
performValidateItems.toggle() // triggers the validation of all items
}) {
Text("Validate")
}
.onChange(of: submitFormFlag, perform: { value in // this never gets called
print(value, "forced")
// if all validation results in the array are true, it will submit the form
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: AddEditProjectViewModel())
}
}
Upvotes: 1
Views: 1091
Reputation: 54601
You shouldn't use didSet
on the @State
- it's a wrapper and it doesn't behave like standard properties.
See SwiftUI — @State:
Declaring the @State
isFilled
variable gives access to three different types:
- isFilled — Bool
- $isFilled — Binding
- _isFilled — State
The State type is the wrapper — doing all the extra work for us — that stores an underlying wrappedValue, directly accessible using isFilled property and a projectedValue, directly accessible using $isFilled property.
Try onChange
for itemsValidationResult
instead:
var body: some View {
HStack {
// ...
}
.onChange(of: itemsValidationResult) { _ in
submitFormFlag.toggle()
}
.onChange(of: submitFormFlag) { value in
print(value, "forced")
}
}
You may also consider putting the code you had in .onChange(of: submitFormFlag)
inside the .onChange(of: itemsValidationResult)
.
Upvotes: 2