How to have a popup alert followed by a navigation push?

Hi! I am trying to implement a button that shows an popup message and then immediately navigate to next view. However, the navigation push does not happen after the alert is displayed. Can you help me to find a solution?


Below is how the code I have looks like:


struct ContentView: View {
    @State var showDetail: Bool = false
    @State private var displayPopupMessage: Bool = false
    
    var body: some View {
       VStack {
           NavigationLink(destination: DetailView(), isActive: self.$showDetail) {EmptyView()}
           Button(action: {showDetail = prepossess()}) {Text(“Alert and Navigate”)}
       }
       .alert(isPresented: self.$displayPopupMessage){
                Alert(title: Text(“Warning”), message: Text(“This is a test”), dismissButton: .default(Text("OK")))}
     }

      private func prepossess() -> Bool {
             self.$displayPopupMessage = true
             return true
      }
}


When I pressed the button, the alert message will show up, but the navigation will not happen, neither before or after dissmissing the alert message. I debugged into the "action" closure of the button, and found the value of the "showDetail" state variable was updated.


I attempted to use the legacy UIAlert from UIKit instead of the new SwiftUI machenism, but still got the same result.


Can you give me some idea how to have the navigation follow the alert?

Accepted Reply

For some reason (may be the playground environment), preprocess() does not work.


But I modified the code and made it work with this:

I put

self.showDetail = true

into the "OK" action


struct ContentView: View {
    @State var showDetail: Bool = false
    @State private var displayPopupMessage: Bool = false
   
    var body: some View {
        VStack {
            NavigationLink(destination: DetailView(), isActive: self.$showDetail) { EmptyView() }
            Button(action: {
                self.displayPopupMessage = true
                    // self.showDetail = true
                    // self.displayPopupMessage = self.prepossess()
                  } ) 
            {
                Text("Alert and Navigate")
            }
        }
        .alert(isPresented: $displayPopupMessage){
            Alert(title: Text("Warning"), message: Text("This is a test"), dismissButton: .default(Text("OK"), action: {
                print("Ok Click")
                self.showDetail = true
            })
            )
        }
    }

//    private func prepossess() -> Bool {
//        self.displayPopupMessage = true
//        return true
//    }

}

Replies

I found a work-around: use .sheet modifier instead of .alert modifier, such that I can use the onDismiss closure to toggle the state variable for navigation link after message is dismissed.


Reference: htt ps:// swiftwithmajid.com/2019/07/24/alerts-actionsheets-modals-and-popovers-in-swiftui/


However, I feel that this solution is pretty complicated, and wondering whether there is any better idea.

When I test this (in playground), It does not show the alert.


Also found a few problems:

you have double quotes

Text(“Alert and Navigate”)

which are not real double quotes, but ” instead of "


You use

self.$displayPopupMessage = true

I get the error: Cannot assign value of type 'Bool' to type 'Binding<Bool>'

should be

self.displayPopupMessage = true


Did you post the exact code ?

For some reason (may be the playground environment), preprocess() does not work.


But I modified the code and made it work with this:

I put

self.showDetail = true

into the "OK" action


struct ContentView: View {
    @State var showDetail: Bool = false
    @State private var displayPopupMessage: Bool = false
   
    var body: some View {
        VStack {
            NavigationLink(destination: DetailView(), isActive: self.$showDetail) { EmptyView() }
            Button(action: {
                self.displayPopupMessage = true
                    // self.showDetail = true
                    // self.displayPopupMessage = self.prepossess()
                  } ) 
            {
                Text("Alert and Navigate")
            }
        }
        .alert(isPresented: $displayPopupMessage){
            Alert(title: Text("Warning"), message: Text("This is a test"), dismissButton: .default(Text("OK"), action: {
                print("Ok Click")
                self.showDetail = true
            })
            )
        }
    }

//    private func prepossess() -> Bool {
//        self.displayPopupMessage = true
//        return true
//    }

}

Thank you Claude31!

The code I initially posted was modified from some complicated original code to present the issue in a simple way, and I didn't put it back to test before posting. Thank you for checking this and correcting it. For other reader's convenience, I pasted your code to a single view project and update it to make sure it works. I got:

struct ContentView: View {
    @State var showDetail: Bool = false
    @State private var displayPopupMessage: Bool = false
     
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView(), isActive: self.$showDetail) { EmptyView() }
                Button(action: {
                    self.displayPopupMessage = true
                }) {Text("Alert and Navigate")}
                .alert(isPresented: $displayPopupMessage){
                    Alert(title: Text("Warning"), message: Text("This is a test"), dismissButton:
                        .default(Text("OK"), action: {self.showDetail = true})
                    )
                }
            }
        }
    }
}

struct DetailView :View {
    var body: some View {Text("details ...")}
}


Above code was tested in a project to be fully working. I added the NavigationView to make sure it works in a separate project.

This solution confirms that each section of code should only make one popup message or navigation change.


Thank you!

Thanks for feedback. It is important as most of us are still on the learning curve of SwiftUI.