swiftswiftui

Popover not initially showing text


I have a popover that displays an error message to the user when input fields aren't filled out. What's happening is my validationMessage is properly being set, but when I go to show my popover, the initial time the popover appears, only a blank box shows.

enter image description here

If I input any field and display it again, it will properly render. Not sure what is causing this issue.

enter image description here

struct SignUpForm: View {
    // General logic for validation
    @State private var onboardingGeneralLogic = OnboardingGeneralLogic()
    
    @Binding var selectedTab: String
    @State var firstName = ""
    @State var lastName = ""
    @State var email: String
    @State var password: String
    @State var confirmPassword: String
    @Binding var isGeneralFormComplete: Bool
    
    @State private var validationMessage = ""
    @State private var showValidationMessage = false
   private var isFormValid: Bool {
    !firstName.isEmpty &&
    !lastName.isEmpty &&
    onboardingGeneralLogic.isValidID(email) &&
    onboardingGeneralLogic.doPasswordsMatch(password, confirmPassword)
}
    private func checkGeneralFormCompletion() {
        if isFormValid {
            isGeneralFormComplete = true
            showValidationMessage = false
        } else {
            isGeneralFormComplete = false
            validationMessage = onboardingGeneralLogic.getErrorMessage(
                firstName: firstName,
                lastName: lastName,
                email: email,
                password: password,
                confirmPassword: confirmPassword
            )
            showValidationMessage = !validationMessage.isEmpty
        }
    }
    
    var body: some View {
        VStack(spacing: 12) {
            Text("General Information")
                .font(Font.custom("Raleway-Regular", size: 20))
                .padding(.top)
                .padding(.bottom, 15)

            TextField("First Name", text: $firstName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.bottom, 10)

            TextField("Last Name", text: $lastName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.bottom, 10)

            TextField("Email Address", text: $email)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.bottom, 10)

            SecureField("Password", text: $password)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.bottom, 10)

            SecureField("Confirm Password", text: $confirmPassword)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding(.bottom, 10)
            
            Button(action: {
                checkGeneralFormCompletion()
            }) {
                Text("Continue")
                    .font(Font.custom("Raleway-Regular", size: 20))
                    .foregroundColor(.white)
                    .padding([.leading, .trailing], 45)
                    .padding([.top, .bottom], 10)
                    .background( Color("PrimaryBlue"))
                    .cornerRadius(5)
            }
            
            .popover(isPresented: self.$showValidationMessage,
                                     attachmentAnchor: .point(.top),
                                     arrowEdge: .top,
                                     content: {
                                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()
    }
}


struct OnboardingGeneralLogic: Codable {
    func getErrorMessage(firstName: String, lastName: String, email: String, password: String, confirmPassword: String) -> String {
        if firstName.isEmpty {
            return "First name cannot be empty."
        } else if lastName.isEmpty {
            return "Last name cannot be empty."
        } else if email.isEmpty || !email.contains("@") {
            return "Invalid email address."
        } else if password != confirmPassword {
            return "Passwords do not match."
        } else if password.count < 6 {
            return "Password must be 6 characters long"
        }
        return "Please enter all fields"
    }
}

Parent View

struct GetStartedMain: View {
    @State private var selectedTab = "General"  // Default tab
    @State private var firstName = ""
    @State private var lastName = ""
    @State private var email = ""
    @State private var password = ""
    @State private var confirmPassword = ""
    @State var isGeneralFormComplete = false

    var body: some View {
        ZStack { // Use ZStack to ensure background fills the entire view
            Color("PrimaryGray") // Background color
                .ignoresSafeArea() // Extend background to ignore safe area
            
            VStack {
                HStack {
                    TabButton(tabName: "General", selectedTab: $selectedTab)
                    Spacer()
                    TabButton(tabName: "Detailed", selectedTab: $selectedTab)
                        .disabled(!isGeneralFormComplete)
                    Spacer()
                    TabButton(tabName: "Picture", selectedTab: $selectedTab)
                }
                .padding(.horizontal)
                .background(Color(.gray.opacity(0.1))) // Background for tabs
                
                if selectedTab == "General" {
                    SignUpForm(
                        selectedTab: $selectedTab,
                        firstName: firstName,
                        lastName: lastName,
                        email: email,
                        password: password,
                        confirmPassword: confirmPassword,
                        isGeneralFormComplete: $isGeneralFormComplete
                    )
                } else if selectedTab == "Detailed" && isGeneralFormComplete {
                    DetailedView()
                } else if selectedTab == "Picture" {
                    ProfilePictureForm()
                }
                Spacer()
            }
        }
    }
}

Solution

  • You need to capture the validationMessage property in your pop over closure so that the latest version is available inside the closure

    .popover(isPresented: self.$showValidationMessage,
             attachmentAnchor: .point(.top),
             arrowEdge: .top,
             content: { [validationMessage] in //<-- here
    
         //...
    }