Tenho um formulário de registro simples. Quando o usuário pressiona Continue Button
, mostro um popover caso ele não digite nenhum campo.
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")
// }
}
}
Se eu comentar a captura forte [validationMessage] in
, quando o usuário pressionar o botão pela primeira vez, o popover mostrará um texto vazio e body
não será renderizado novamente (com base na _printChanges
chamada). Mas se eu fornecer captura forte para closure
ou .popover
adicionar .onChange
modificador a VStack
, o corpo será renderizado novamente e o popover mostrará o texto correto.
Minha pergunta é por que quando eu forneço captura forte de @State
, isso causa body
a renderização novamente? Quaisquer insights ou sugestões seriam apreciados!
O SwiftUI tem um recurso de rastreamento de dependência. Ele só chamará
body
novamente quando a@State
for definido se seu getter tiver sido chamado anteriormente dentro debody
. Fazer[validationMessage] in
é fingir uma chamada para o getter que configura o rastreamento de dependência e é por issobody
que é chamado quando ele muda. Normalmente, isso significa que sua fonte de verdade está errada. Parece que no seu caso você está usando uma variável booleana como fonte de verdade, mas deveria quando há uma mensagem para mostrar. Você pode criar umaBinding<Bool>
variável computada a partir da mensagem para usar com o popover ou usar o novopopover(item:)
em vez disso.O problema central está em como o
showValidationMessage
está sendo usado como gatilho para opopover
, enquanto o estado significativo real (validationMessage
) não é consistentemente integrado ao sistema de dependências do SwiftUI.A abordagem atual (forçar captura forte) é uma solução alternativa em vez de um design limpo. Em vez disso, considere as seguintes soluções:
popover(item:)
A
popover(item:)
API é projetada para mostrar conteúdo com base em um item identificável opcional. Você pode fazervalidationMessage
um opcionalString
:showValidationMessage
Em vez de gerenciar explicitamente
showValidationMessage
, derive-o diretamente devalidationMessage
:checkGeneralFormCompletion
para garantirvalidationMessage
que seja a única fonte da verdade:popover
para confiar emshowValidationMessage
: