Je sais comment @Environment (\.presentationMode) var presentationMode / self.presentationMode.wrappedValue.dismiss() un modal d'une vue enfant en utilisant @Environment (\.presentationMode) var presentationMode / self.presentationMode.wrappedValue.dismiss() mais c'est un problème différent.
Lorsque vous présentez un NavigationView plusieurs pages dans une fenêtre modale et que vous avez parcouru quelques pages, la référence à presentationMode change pour être NavigationView, donc l'utilisation de self.presentationMode.wrappedValue.dismiss() simplement le dernier NavigationView au lieu de rejetant le modal contenant.
Est-il possible - et si oui comment - de supprimer le modal contenant d'une page dans une arborescence NavigationView?
Voici un exemple simple illustrant le problème. Si vous créez un Xcode unique Voir le projet de l' application à l' aide SwiftUI et remplacez la valeur par défaut ContentView code avec cela, il devrait fonctionner sans autre modification.
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")
}
}
}
3 Réponses :
Voici une approche possible basée sur l'utilisation de la propre clé d'environnement explicitement créée (en fait, j'ai le sentiment qu'il n'est pas correct d'utiliser presentationMode pour ce cas d'utilisation .. de toute façon).
L'approche proposée est générique et fonctionne à partir de n'importe quelle vue dans la hiérarchie des vues modales. Testé et fonctionne avec 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")
}
}
}
Merci - je peux confirmer que cela fonctionne parfaitement. Je vais juste attendre et voir s'il y a d'autres approches ou commentaires sur cette approche, mais en supposant qu'il n'y a pas d'inconvénients, je marquerai bientôt comme la bonne réponse.
J'ai découvert que vous pouvez réellement faire de showModal un EnvironmentObject, puis simplifier le basculement de showModal sur false sur PageTwoContent pour rejeter à la fois PageOneContent et PageTwoContent.
Une autre approche consisterait simplement à utiliser une notification pour ce cas et à réinitialiser simplement l'indicateur de déclenchement de votre modal. Ce n'est pas la plus belle solution pour moi mais c'est la solution que je suis le plus susceptible de comprendre encore dans quelques mois.
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
}
}
}
Si vous ne voulez pas coupler vaguement les vues via une notification, vous pouvez également simplement utiliser une liaison pour ceci comme ceci:
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")))
}
}
}
J'apprends swiftui depuis quelques semaines, donc je ne suis pas sûr, mais avez-vous essayé de remplacer "trailing: NavigationLink (destination: PageTwoContent ()) {" par trailing: PageTwoContent () {. Ou supprimez le bloc NavigationView dans PageTwoContent
@SimonePistecchia J'ai peur qu'aucune de vos suggestions ne soit appropriée, mais merci quand même
Intéressant ...
UIKit-3900.12.15/UINavigationController.m:8129 CRASH: Tried to pop to a view controller that doesn't exist.exception avec votre instantanéUIKit-3900.12.15/UINavigationController.m:8129 CRASH: Tried to pop to a view controller that doesn't exist.. N'est-ce pas?@Asperi - non, ça marche bien. Bien que je viens de faire un changement pour le faire fonctionner avec un projet Xcode SwiftUI par défaut prêt à l'emploi.