'PopUp don't want to dismiss SwiftUI

I am having a problem while I want to dismiss a popup (that appears automatically depending on a specific condition) by clicking a button. This is the PopUp struct:

struct dataPrivacyPopUp: View {
    
    let model: OffersView.Model
    let termsOfUseText = "Nutzungsbedingungen"
    let privacyPolicyText = "Datenschutzerklärung"
    @State var termsOfUseChecked = false
    @State var privacyPolicyChecked = false
    @State var buttonDisabled = true
    @State private var showPopUp: Bool = false
    @Binding var showModal: Bool


    var body: some View {
        ZStack {
            if ( model.showPopUp == true) {
        // PopUp Window
        VStack(alignment: .center){
            Image("logo")
                .aspectRatio(contentMode: .fill)
                .frame(alignment: .center)
                .padding()
            
            VStack(alignment: .leading) {
                Text((model.acceptance?.salutation)!)
                    .multilineTextAlignment(.leading)
                    .padding()
                    .foregroundColor(Color.black)
                Text((model.acceptance?.statement)!)
                    .multilineTextAlignment(.leading)
                    .padding()
                    .foregroundColor(Color.black)
                Text((model.acceptance?.declarationIntro)!)
                    .multilineTextAlignment(.leading)
                    .padding()
                    .foregroundColor(Color.black)
                
                    
                    if ((model.acceptance?.dpr)! == true) {
                        VStack(alignment: .leading){
                        HStack {
                            CheckBoxView(checked: $privacyPolicyChecked)
                            HStack(spacing: 0){
                                Text(R.string.localizable.dataPrivacyPopupText())
                                    .foregroundColor(Color.black)
                                Button(privacyPolicyText) {
                                    model.openUrl(url: API.privacyPolicyURL)
                                }
                            }
                        }
                            Text((model.acceptance?.declarationOutro)!)
                                .multilineTextAlignment(.leading)
                                .padding()
                        }
                        .padding()
                        Button(action: {
                            model.setTos()
                            print("showModal PopUpView2 1: \(showModal)")

                            self.showModal.toggle()
                            print("showModal PopUpView2 2: \(showModal)")

                        }, label: {
                            Text(R.string.localizable.dataPrivacyButton())
                                .foregroundColor(Color.white)
                                .font(Font.system(size: 23, weight: .semibold))
                        })
                            .disabled(model.buttonDisabledForOne(privacyPolicyChecked: privacyPolicyChecked, termsOfUseChecked: termsOfUseChecked))
                            .padding()

                    }
                    
            }
        }
//        .onAppear(perform: )
        .background(Color.white01)
        .padding()
    }
        }
    }
}

and this is where I call it (contentView):

struct OffersView: View {
    @StateObject var model = Model()
    @State private var showingPopUp = false
    @State private var showModal = false
    @State private var showingAddUser = false

    //    var showPopup : Bool = true
    var body: some View {
        NavigationView {
            Group {
                switch model.sections {
                case .loading:
                    ActivityIndicator(animate: true)
                case .success(let sections):
                    ScrollView(.vertical) {
                        VStack(alignment: .leading, spacing: 0) {
                            Text(R.string.localizable.offersHello(model.firstName))
                                .aplFont(.headline02)
                                .padding(.bottom, 24)
                            
                            VStack(spacing: 48) {
                                ForEach(sections) { section in
                                    OffersSectionView(section: section, model: model)
                                }
                            }
                        }
                        .useFullWidth(alignment: .leading)
                        .padding()
                    }
                default:
                    Color.clear
                    if ( model.showPopUp == true) {
                    ZStack {
                            Color.black.opacity(model.showPopUp ? 0.3 : 0).edgesIgnoringSafeArea(.all)
                        dataPrivacyPopUp(model: model, showModal: self.$showModal)
                            .onAppear(perform: {
                                self.showModal.toggle()
                            })
                        }
                    }
                }
            }
            .navigationBarHidden(true)
            .handleNavigation(model.navigationPublisher)
            .onAppear(perform: model.onAppear)
            .onDisappear(perform: model.onDisappear)
            .environment(\.dynamicTypeEnabled, false)
            .safariView(isPresented: model.showSafari) {
                SafariView(url: model.safariUrl!)
            }
        }
    }
}

I need help about this, I tried the traditional method to set a @Binding variable etc .. but that's not working, the boolean value is changing but the UI is not updating (the popup is not dismissing), thank you



Solution 1:[1]

I tried to look at your code - I suggest you simplify it to the bare minimum to exemplify your issue - and it seems that you are using 2 properties to show your pop-up: showingPopUp and showModal. It is quite likely that you are having trouble keeping them both in sync.

For starters, I would suggest to use only one variable, either it is true or false - "a man with two watches never knows what time it is".

For the solution:

If you prefer keeping your ZStack approach, the solution would look something like:

struct MyPrivacy: View {
    
    @Binding var showMe: Bool
    
    var body: some View {
        VStack {
            Text("The content of the pop-up")
                .padding()
            
            Button {
                withAnimation {
                    showMe.toggle()
                }
            } label: {
                Text("Dismiss")
            }
        }
    }
}

struct Offers: View {
    
    @State private var showPopup = false
    
    var body: some View {
        NavigationView {
            ZStack {
                VStack {
                    Text("View behind the pop-up")
                        .padding()
                    
                    Button {
                        withAnimation {
                            showPopup.toggle()
                        }
                    } label: {
                        Text("Pop")
                    }
                }
                
                if showPopup {
                    Color.white
                    MyPrivacy(showMe: $showPopup)
                }
            }
        }
    }
}

If instead you want to go for a more flexible approach, if you are developing for iOS, SwiftUI has a convenient object - Sheets. You can use it as suggested in the documentation, or build a specific struct that manages all the modal views of this type and use your model to handle the presentation.

The process goes like:

  1. Create a struct that will handle all kinds of Sheets of your app.
  2. Add to your view-model the property to present any sheet.
  3. Create the Views that will be the content of each sheet.
  4. Call the .sheet(item:content:) method on each View the requires a sheet.

Here's the sample code:

  1. SheetView handler:
struct SheetView: Identifiable {
    
    // This struct controls what modal view will be presented.
    // The enum SheetScreenType can grow to as many as different
    // modal views your app needs - add the content in the switch below.
    
    let id = UUID()
    var screen: SheetScreenType
    
    @ViewBuilder
    var content: some View {
        switch screen {
        case .dataPrivacy:
            DataPrivacy()
        default:
            EmptyView()
        }
    }
    
    enum SheetScreenType {
        case dataPrivacy
        case none
    }
}
  1. Presenter in your view-model:
class MyViewModel: ObservableObject {
    
    // This code can fit anywhere within your view-model.
    // It controls the presentation of the modal view, which in
    // this case is a Sheet.
    
    private let sharedSheet = SheetView(screen: .none)
    
    // Show the selected sheet
    @Published var sheetView: SheetView?
    
    var showSheet: SheetView.SheetScreenType {
        get {
            return sheetView?.screen ?? .none
        }
        set {
            switch newValue {
            case .none:
                sheetView = nil
            default:
                sheetView = sharedSheet
            }
            
            sheetView?.screen = newValue
        }
    }
}
  1. Content of your modal view:
struct DataPrivacy: View {
    @EnvironmentObject var model: MyViewModel // Pass YOUR model here
    
    var body: some View {
            VStack(alignment: .center){
                Text("Respecting your privacy, no details are shown here")
                    .padding()
                
                Button {
                    print("Anything you need")
                    
                    // Set the showSheet property of your model to
                    // present a modal view. Setting it to .none dismisses
                    // the modal view.
                    model.showSheet = .none
                    
                } label: {
                    Text("Time do dismiss the modal view")
                }
                    .padding()
        }
    }
}
  1. Enable your view to listen to your model to present the sheet:
struct OffersView: View {
    @ObservedObject var model = MyViewModel() // Pass YOUR model here
    
    var body: some View {
        VStack {
            Text("Anything you wish")
                .padding()
            
            Button {
                withAnimation {
                    
                    // Set the showSheet property of your model to
                    // present a modal view. Set it to any choice
                    // among the ones in the SheetScreen.SheetScreenType enum.
                    model.showSheet = .dataPrivacy
                    
                }
            } label: {
                Text("Tap here for the privacy in modal view")
            }
        }

        // Show a modal sheet.
        // Add this property at the top level of every view that
        // requires a modal view presented - whatever content it might have.
        .sheet(item: $model.sheetView) { sheet in
            sheet.content
                .environmentObject(model)
        }
    }
}

Good luck with your project!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1