Dismiss a parent modal in SwiftUI from a NavigationView

Another Approach would be to simply use a notification for this case and just reset the triggering flag for your modal. It is not the most beautiful solution for me but it is the solution I am most likely to still understand in a few months.

import SwiftUI

struct ContentView: View {
    @State var showModalNav: Bool = false
    
    var body: some View {
        Text("Present Modal")
            .padding()
            .onTapGesture {
                showModalNav.toggle()
            }.sheet(isPresented: $showModalNav, content: {
                ModalNavView()
            }).onReceive(NotificationCenter.default.publisher(for: Notification.Name(rawValue: "PushedViewNotifciation"))) { _ in
                showModalNav = false
            }
    }
}


struct ModalNavView: View {
    var body: some View {
        NavigationView {
            NavigationLink(
                destination: PushedView(),
                label: {
                    Text("Show Another View")
                }
            )
        }
    }
}

struct PushedView: View {
    var body: some View {
        Text("Pushed View").onTapGesture {
            NotificationCenter.default.post(Notification.init(name: Notification.Name(rawValue: "PushedViewNotifciation")))
        }
    }
}

If you don't want to loosely couple the views through a notification you could also just use a binding for this like so:

struct ContentView: View {
    @State var showModalNav: Bool = false
    
    var body: some View {
        Text("Present Modal")
            .padding()
            .onTapGesture {
                showModalNav.toggle()
            }.sheet(isPresented: $showModalNav, content: {
                ModalNavView(parentShowModal: $showModalNav)
            }).onReceive(NotificationCenter.default.publisher(for: Notification.Name(rawValue: "PushedViewNotifciation"))) { _ in
                showModalNav = false
            }
    }
}


struct ModalNavView: View {
    @Binding var parentShowModal: Bool
    var body: some View {
        NavigationView {
            NavigationLink(
                destination: PushedView(parentShowModal: $parentShowModal),
                label: {
                    Text("Show Another View")
                }
            )
        }
    }
}

struct PushedView: View {
    @Binding var parentShowModal: Bool
    var body: some View {
        Text("Pushed View").onTapGesture {
            parentShowModal = false
        }
    }
}

Here is possible approach based on usage own explicitly created environment key (actually I have feeling that it is not correct to use presentationMode for this use-case.. anyway).

Proposed approach is generic and works from any view in modal view hierarchy. Tested & works with Xcode 11.2 / iOS 13.2.

// define env key to store our modal mode values
struct ModalModeKey: EnvironmentKey {
    static let defaultValue = Binding<Bool>.constant(false) // < required
}

// define modalMode value
extension EnvironmentValues {
    var modalMode: Binding<Bool> {
        get {
            return self[ModalModeKey.self]
        }
        set {
            self[ModalModeKey.self] = newValue
        }
    }
}


struct ParentModalTest: View {
  @State var showModal: Bool = false

  var body: some View {
    Button(action: {
      self.showModal.toggle()
    }) {
      Text("Launch Modal")
    }
    .sheet(isPresented: self.$showModal, onDismiss: {
    }) {
      PageOneContent()
        .environment(\.modalMode, self.$showModal) // < bind modalMode
    }
  }
}

struct PageOneContent: View {
  var body: some View {
    NavigationView {
      VStack {
        Text("I am Page One")
      }
      .navigationBarTitle("Page One")
      .navigationBarItems(
        trailing: NavigationLink(destination: PageTwoContent()) {
          Text("Next")
        })
      }
  }
}

struct PageTwoContent: View {

  @Environment (\.modalMode) var modalMode // << extract modalMode

  var body: some View {
    NavigationView {
      VStack {
        Text("This should dismiss the modal. But it just pops the NavigationView")
          .padding()

        Button(action: {
          self.modalMode.wrappedValue = false // << close modal
        }) {
          Text("Finish")
        }
        .padding()
        .foregroundColor(.white)
        .background(Color.blue)
      }
      .navigationBarTitle("Page Two")
    }
  }
}

Tags:

Ios

Swiftui