I have a simple register form, when user press Continue Button
, I will show a popover if they not type in any fields.
struct ContentView: View {
@State var firstName = ""
@State var lastName = ""
@State private var validationMessage = ""
@State private var showValidationMessage = false
private var isFormValid: Bool {
!firstName.isEmpty &&
!lastName.isEmpty
}
private func checkGeneralFormCompletion() {
if isFormValid {
showValidationMessage = false
} else {
let errorText = {
if firstName.isEmpty {
return "First name empty"
} else if lastName.isEmpty {
return "Last name empty"
} else {
return ""
}
}()
validationMessage = errorText
showValidationMessage = !errorText.isEmpty
}
}
var body: some View {
VStack(spacing: 12) {
let _ = Self._printChanges()
TextField("First Name", text: $firstName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom, 10)
TextField("Last Name", text: $lastName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.bottom, 10)
Button(action: {
checkGeneralFormCompletion()
}) {
Text("Continue")
.foregroundColor(.white)
.padding(.horizontal, 45)
.padding([.top, .bottom], 10)
.background( Color.red)
.cornerRadius(5)
}
.popover(isPresented: self.$showValidationMessage,
attachmentAnchor: .point(.top),
arrowEdge: .top,
content: { [validationMessage] in
let _ = print("validationMessage :: popover :: ", validationMessage)
VStack {
Text(validationMessage)
}
.multilineTextAlignment(.center)
.lineLimit(0)
.foregroundStyle(.black)
.font(.system(size: 18, weight: .semibold, design: .rounded))
.padding()
.presentationCompactAdaptation(.none)
.fixedSize(horizontal: false, vertical: true)
.frame(minWidth: 200)
})
.padding(.top, 70)
}
.padding()
// .onChange(of: validationMessage) { oldValue, newValue in
// print("CHANGE text VALIDATE MSG")
// }
// .onChange(of: showValidationMessage) { oldValue, newValue in
// print("CHANGE VALIDATE MSG")
// }
}
}
If I comment out the strong capture [validationMessage] in
, when user first press button, popover will show an empty text and body
is not rerender ( base on _printChanges
call ). But if I provide strong capture to closure
of .popover
or add .onChange
modifier to VStack
, the body will be rerender and popover will show correct text.
My question is why when I provide strong capture of @State
, it cause body
to rerender? Any insights or suggestions would be appreciated!
SwiftUI has a dependency tracking feature. It will only call body
again when a @State
is set if its getter was previously called within body
. Doing [validationMessage] in
is faking a call to the getter which sets up the depdendency tracking and is why body
is called when it changes. Usually this means your source of truth is wrong. Seems in your case you are using a boolean var as source of truth but it should actually when there is a message to show. You can either make a Binding<Bool>
computed var from the message to use with the popover or use the new popover(item:)
instead.