Dismiss a Parent Modal in Swiftui from a Navigationview

Dismiss a parent modal in SwiftUI from a NavigationView

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")
}
}
}

How to dismiss a SwiftUI NavigationView from a parent view

Here is the solution:

struct SwiftUIView: View {
@State var isPresent = true
@State var isDismissed: Bool = false

var body: some View {
HStack {
Button(action: {
isDismissed = true
}) {
Text("Dismiss View")
} // assume this button is always visible

NavigationView {
VStack {
Text("This is the NavigationView")
NavigationLink {
ViewToDismiss(dismissView: $isDismissed)
} label: {
Text("Go to view I want to exit")
}
}
}

}
}
}

struct ViewToDismiss: View {
@Environment(\.dismiss) private var dismiss
@Binding var dismissView: Bool
var body: some View {
Text("This is the view I want to exit")
.onChange(of: dismissView,
perform:
( { newValue in
dismiss()
}))
}
}

How to dismiss a presenting view to the root view of tab view in SwiftUI?

The presentationMode is one-level effect value, ie changing it you close one currently presented screen.

Thus to close many presented screens you have to implement this programmatically, like in demo below.

The possible approach is to use custom EnvironmentKey to pass it down view hierarchy w/o tight coupling of every level view (like with binding) and inject/call only at that level where needed.

Demo tested with Xcode 12.4 / iOS 14.4

demo

struct ContentView: View {
var body: some View {
TabView {
Text("Tab1")
.tabItem { Image(systemName: "1.square") }
Tab2RootView()
.tabItem { Image(systemName: "2.square") }
}
}
}

struct Tab2RootView: View {
@State var toRoot = false
var body: some View {
NavigationView {
Tab2NoteView(level: 0)
.id(toRoot) // << reset to root !!
}
.environment(\.rewind, $toRoot) // << inject here !!
}
}

struct Tab2NoteView: View {
@Environment(\.rewind) var rewind
let level: Int

@State private var showFullScreen = false
var body: some View {
VStack {
Text(level == 0 ? "ROOT" : "Level \(level)")
NavigationLink("Go Next", destination: Tab2NoteView(level: level + 1))
Divider()
Button("Full Screen") { showFullScreen.toggle() }
.fullScreenCover(isPresented: $showFullScreen,
onDismiss: { rewind.wrappedValue.toggle() }) {
Tab2FullScreenView()
}
}
}
}

struct RewindKey: EnvironmentKey {
static let defaultValue: Binding<Bool> = .constant(false)
}

extension EnvironmentValues {
var rewind: Binding<Bool> {
get { self[RewindKey.self] }
set { self[RewindKey.self] = newValue }
}
}

struct Tab2FullScreenView: View {
@Environment(\.presentationMode) var mode

var body: some View {
Button("Close") { mode.wrappedValue.dismiss() }
}
}

SwiftUI Dismiss Multiple Modal Sheets

You can pass showModal as a binding into the following screens and instead of using presentationValue set showModal to false.



Related Topics



Leave a reply



Submit