skip to Main Content

So I have a problem regarding NavigationView and NavigationLinks in SwiftUI while trying to pop the current screen, which is not working in this case.

I have 3 Screens A -> B -> C

Each screen has a button for popping itself.

The pop from B -> A works, but when you navigate from A -> B -> C, the pop from C -> B it’s not working.

Do you know what would be the problem? I will attach the code below and a video.
The app should support iOS 14.

enter image description here

struct ScreenA: View {
    @State private var showB = false
    
    var body: some View {
        NavigationView {
            VStack {
                Button("Go to B") {
                    showB = true
                }
                
                NavigationLink(destination: ScreenB(didTapGoBack: {
                    showB = false
                }), isActive: $showB) {
                    EmptyView()
                }
            }
        }
    }
}
struct ScreenB: View {
    var didTapGoBack: () -> Void
    @State var showC = false

    var body: some View {
        VStack {
            Button("Go to C") {
                showC = true
            }
        
            Button("Back to Screen A") {
                didTapGoBack()
            }
            
            NavigationLink(destination: ScreenC(didTapGoBack: {
                showC = false
            }), isActive: $showC) {
                EmptyView()
            }
        }
    }
}
struct ScreenC: View {
    var didTapGoBack: () -> Void
    
    var body: some View {
        VStack {
            Text("Screen C")
            Button("Back to Screen B") {
                didTapGoBack()
            }
        }
    }
}
@main
struct TestNavApp: App {
    var body: some Scene {
        WindowGroup {
            ScreenA()
        }
    }
}

2

Answers


  1. Chosen as BEST ANSWER

    In addition to @La pieuvre answer, to make this handling even easier on iOS 14 (like the handling for iOS 15 +). We can create an extension for EnvironmentValues

    extension EnvironmentValues {
        var dismiss: () -> Void {
            { presentationMode.wrappedValue.dismiss() }
        }
    }
    

    Then we can use it in a more cleaner way, like in iOS 15+

    struct ScreenC: View {
        @Environment(.dismiss) var dismiss
        
        var body: some View {
            VStack {
                Text("Screen C")
                Button("Back to Screen B") {
                    dismiss()
                }
            }
        }
    }
    

    So we can get rid of calling presentationMode.wrappedValue.dismiss() all the time


  2. There is a better way to do that and it has the advantage to work 🙂

    you can use the @environment(.dismiss) property and call it to dismiss your current view. It makes your code simpler as well. Here is the result :

    import SwiftUI
    
    struct ScreenA: View {
        @State private var showB = false
        
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink(destination: ScreenB(), isActive: $showB) {
                        Text("Go to B")
                    }
                }
            }
        }
    }
    
    struct ScreenB: View {
        @Environment(.dismiss) var dismiss
        @State var showC = false
        
        var body: some View {
            VStack {
                
                NavigationLink(destination: ScreenC(), isActive: $showC) {
                    Text("Go to C")
                }
                
                Button("Back to Screen A") {
                    dismiss()
                }
            }
        }
    }
    
    struct ScreenC: View {
        @Environment(.dismiss) var dismiss
        
        var body: some View {
            VStack {
                Text("Screen C")
                Button("Back to Screen B") {
                    dismiss()
                }
            }
        }
    }
    

    Note that NavigationLink(destination:isActive:) is deprecated.

    Here is the version with the updated code :

    struct ScreenA: View {
        @State private var showB = false
        
        var body: some View {
            NavigationStack {
                VStack {
                    Button("Go to B") {
                        showB = true
                    }
                }
                .navigationDestination(isPresented: $showB ){
                    ScreenB()
                }
            }
            
        }
    }
    
    struct ScreenB: View {
        @Environment(.dismiss) var dismiss
        @State var showC = false
        
        var body: some View {
            VStack {
                
                Button("Go to C"){
                    showC = true
                }
                
                Button("Back to Screen A") {
                    dismiss()
                }
            }
            .navigationDestination(isPresented: $showC ){
                ScreenC()
            }
        }
    }
    
    struct ScreenC: View {
        @Environment(.dismiss) var dismiss
        
        var body: some View {
            VStack {
                Text("Screen C")
                Button("Back to Screen B") {
                    dismiss()
                }
            }
        }
    }
    

    [Edit] for iOS 14 support we need to use @Environment(.presentationMode) var presentationMode as you pointed out in your comment

    Here is the iOS 14 version of the code :

    struct ScreenA: View {
        @State private var showB = false
        
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink(destination: ScreenB(), isActive: $showB) {
                        Text("Go to B")
                    }
                }
            }
        }
    }
    
    struct ScreenB: View {
        @Environment(.presentationMode) var presentationMode
        @State var showC = false
       
        var body: some View {
            VStack {
                
                NavigationLink(destination: ScreenC(), isActive: $showC) {
                    Text("Go to C")
                }
                
                Button("Back to Screen A") {
                    presentationMode.wrappedValue.dismiss()
                }
            }
        }
    }
    
    struct ScreenC: View {
        @Environment(.presentationMode) var presentationMode
        
        var body: some View {
            VStack {
                Text("Screen C")
                Button("Back to Screen B") {
                    presentationMode.wrappedValue.dismiss()
                }
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search