Dismiss a parent modal in SwiftUI from a Navigatio

2020-03-30 06:47发布

问题:

I am aware of how to dismiss a modal from a child view using @Environment (\.presentationMode) var presentationMode / self.presentationMode.wrappedValue.dismiss() but this is a different issue.

When you present a multi-page NavigationView in a modal window, and have navigated through a couple of pages, the reference to presentationMode changes to be the NavigationView, so using self.presentationMode.wrappedValue.dismiss() simply pops the last NavigationView rather than dismissing the containing modal.

Is it possible - and if so how - to dismiss the containing modal from a page in a NavigationView tree?

Here's a simple example showing the problem. If you create an Xcode Single View app project using SwiftUI and replace the default ContentView code with this, it should work with no further changes.

import SwiftUI

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

  var body: some View {
    Button(action: {
      self.showModal.toggle()
    }) {
      Text("Launch Modal")
    }
    .sheet(isPresented: self.$showModal, onDismiss: {
      self.showModal = false
    }) {
      PageOneContent()
    }
  }
}

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 (\.presentationMode) var presentationMode

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

        Button(action: {
          // How to dismiss parent modal here instead
          self.presentationMode.wrappedValue.dismiss()
        }) {
          Text("Finish")
        }
        .padding()
        .foregroundColor(.white)
        .background(Color.blue)
      }
      .navigationBarTitle("Page Two")
    }
  }
}

回答1:

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


标签: ios swiftui