Puisque SwiftUI est déclaratif, il n'y a pas de méthode de dismiss
. Comment puis- DetailView
ajouter un bouton de rejet / fermeture à DetailView
?
struct DetailView: View { var body: some View { Text("Detail") } } struct ContentView : View { var body: some View { PresentationButton(Text("Click to show"), destination: DetailView()) } }
13 Réponses :
Voici un moyen de supprimer la vue présentée.
struct DetailView: View { @Binding var dismissFlag: Bool var body: some View { Group { Text("Detail") Button(action: { self.dismissFlag.toggle() }) { Text("Dismiss") } } } } struct ContentView : View { @State var dismissFlag = false var body: some View { Button(action: { self.dismissFlag.toggle() }) { Text("Show") } .presentation(!dismissFlag ? nil : Modal(DetailView(dismissFlag: $dismissFlag)) { print("dismissed") }) } }
Merci, mais si l'utilisateur fait glisser pour ignorer, la bascule doit appuyer deux fois. Peut être contourné en changeant l'état self.dismissFlag = true; self.dismissFlag = false;
. Solution de contournement, pas de solution. Cherche également un moyen de désactiver le glissement pour ignorer.
Je pense que si vous implémentiez onDismiss
dans le constructeur Modal
, vous seriez en mesure de maintenir la synchronisation de dismissFlag
. Je n'ai pas essayé pour être sûr.
Pour vérifier cela, je viens de tester ce qui se passe avec self.dismissFlag
lors du rejet de la vue en utilisant le mouvement de glissement. Ajoutez onDismiss: { print(self.dismissFlag) }
à votre .sheet pour vous tester. Il semble qu'il bascule automatiquement la variable lors du glissement. Notez que la fonction onDismiss ne semble être appelée que lors du déplacement de la vue modale. Si vous fermez le modal en basculant le self.dismissFlag
vous le onDismiss
n'est pas appelé. (Je suis sur iOS 13 Beta 8)
Depuis PresentationButton
est facile à utiliser mais cachant l'état qui mine le caractère prédictif de SwiftUI
je l'ai implémenté avec une Binding
accessible.
struct DetailView: View { @Binding var showModal: Bool var body: some View { Group { Text("Detail") Button(action: { self.showModal = false }) { Text("Dismiss") } } } } struct ContentView: View { @State var showModal = false var body: some View { BindedPresentationButton( showModal: $showModal, label: Text("Show"), destination: DetailView(showModal: $showModal) ) { print("dismissed") } } }
Voici comment il est utilisé:
public struct BindedPresentationButton<Label, Destination>: View where Label: View, Destination: View { /// The state of the modal presentation, either `visibile` or `off`. private var showModal: Binding<Bool> /// A `View` to use as the label of the button. public var label: Label /// A `View` to present. public var destination: Destination /// A closure to be invoked when the button is tapped. public var onTrigger: (() -> Void)? public init( showModal: Binding<Bool>, label: Label, destination: Destination, onTrigger: (() -> Void)? = nil ) { self.showModal = showModal self.label = label self.destination = destination self.onTrigger = onTrigger } public var body: some View { Button(action: toggleModal) { label } .presentation( !showModal.value ? nil : Modal( destination, onDismiss: { self.toggleModal() } ) ) } private func toggleModal() { showModal.value.toggle() onTrigger?() } }
Ne fonctionne pas pour SwiftUI 2 - Modal est obsolète
Vous pouvez l'implémenter.
struct view: View { @Environment(\.isPresented) private var isPresented private func dismiss() { isPresented?.value = false } }
Merci pour l'indice avec l'environnement. Comment accéder à isPresented
pour l'extérieur comme dans mon exemple?
C'est faux. Vous devriez passer une variable d'état qui est également utilisée pour isPresented, plutôt que de jouer avec le presentationMode.
struct ContentView: View { @State private var showModal = false // If you are getting the "can only present once" issue, add this here. // Fixes the problem, but not sure why; feel free to edit/explain below. @Environment(\.presentationMode) var presentationMode var body: some View { Button(action: { self.showModal = true }) { Text("Show modal") }.sheet(isPresented: self.$showModal) { ModalView() } } } struct ModalView: View { @Environment(\.presentationMode) private var presentationMode var body: some View { Group { Text("Modal view") Button(action: { self.presentationMode.wrappedValue.dismiss() }) { Text("Dismiss") } } } }
Vous pouvez utiliser la variable d'environnement presentationMode
dans votre vue modale et appeler self.presentaionMode.wrappedValue.dismiss()
pour self.presentaionMode.wrappedValue.dismiss()
le modal:
struct ContentView: View { @State private var showModal = false var body: some View { Button("Show Modal") { self.showModal.toggle() }.sheet(isPresented: $showModal) { ModalView(showModal: self.$showModal) } } } struct ModalView: View { @Binding var showModal: Bool var body: some View { Text("Modal view") Button("Dismiss") { self.showModal.toggle() } } }
Merci pour l'indice avec l'environnement. Comment accéder à isPresented
pour l'extérieur comme dans mon exemple?
C'est une nouvelle pour moi. Merci!
Bonne trouvaille! Cependant, pour moi (en utilisant Xcode 11 Beta 3), cela ne fonctionne qu'une seule fois pour moi lorsque PresentationLink est utilisé dans un List ou un navigationBarItems. Je peux présenter et ignorer la vue une fois à partir de chaque bouton.
J'ai également expérimenté la bêta 3 "présente une seule fois" si vous utilisez un problème de liste. Cependant, la bêta 4 semble avoir interrompu la capacité du Modal à se rejeter avec la variable d'environnement isPresented dans certains cas. L'exemple ci-dessus fonctionne toujours, mais mon exemple ne fonctionne pas. J'essaie toujours d'isoler le problème.
À quoi ressemble la vue qui présente ModalView?
Je remarque dans Xcode Version 11.0 (11A419c)
que lors de l' utilisation self.presentationMode.wrappedValue.dismiss()
s'appelle que la onDismiss
fonction sur .sheet(
pas appelé. Quand je rejette le point de vue modal en tirant vers le bas le rappel est appelé.
Vous pouvez également simplement utiliser @Environment(\.presentationMode) var presentationMode
puisque Swift déduira le type via le chemin de clé spécifié.
C'est faux. Vous devriez passer une variable d'état qui est également utilisée pour isPresented, plutôt que de jouer avec le presentationMode.
Je suis d'accord avec @ stardust4891. Vous devez passer une variable d'état. Utilisez la réponse ci-dessous. Cela pourrait causer des problèmes à un stade ultérieur. Par exemple en utilisant avec un TabView.
Je pense qu'il vaut mieux passer une variable @State
plutôt que d'utiliser PresentationMode
. PresentationMode
ne rejettera pas toujours le modal. Par exemple, si vous avez un NavigationView
dans votre modal comme dans cette réponse , alors l'appel de la fonction dismiss()
n'apparaîtra à la vue précédente que si vous avez navigué vers un autre écran.
J'ai utilisé l'approche de @Environment(\.presentationMode)
et la fermeture des modaux se comporte maintenant mal d'une manière imprévisible. Revenir au passage de la variable d'état à modal.
Il existe maintenant un moyen assez simple de le faire dans la bêta 5.
import SwiftUI struct ModalView : View { // In Xcode 11 beta 5, 'isPresented' is deprecated use 'presentationMode' instead @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> var body: some View { Group { Text("Modal view") Button(action: { self.presentationMode.wrappedValue.dismiss() }) { Text("Dismiss") } } } } struct ContentView : View { @State var showModal: Bool = false var body: some View { Group { Button(action: { self.showModal = true }) { Text("Show modal via .sheet modifier") } .sheet(isPresented: $showModal, onDismiss: { print("In DetailView onDismiss.") }) { ModalView() } } } }
C'est faux. Vous devriez passer une variable d'état qui est également utilisée pour isPresented, plutôt que de jouer avec le presentationMode.
Faux dans quel contexte? Votre problème est-il une question d'exactitude ou une préférence de style? Il existe plusieurs autres façons d'accomplir la même tâche qui fonctionnent également tout aussi bien. Les propres notes de publication iOS 13 d'Apple documentent cela comme une méthode pour rejeter les modaux et cela fonctionne. Merci.
C'est un aperçu intéressant, mais cela peut ou non être un réel problème. Il a été expliqué au début était que rejeter () avait été ajouté pour plus de commodité, de sorte qu'une liaison à la var isPresented n'aurait pas à être transmise à la vue modale en plus du modificateur de feuille. Tout ce qu'il fait est de définir la variable isPresented sur false si elle est vraie, sinon (selon le fichier d'en-tête SwiftUI), elle ne fait rien.
Je pense qu'il vaut mieux passer une variable @State
plutôt que d'utiliser PresentationMode
. PresentationMode
ne rejettera pas toujours le modal. Par exemple, si vous avez un NavigationView
dans votre modal comme dans cette réponse , alors l'appel de la fonction dismiss()
n'apparaîtra à la vue précédente que si vous avez navigué vers un autre écran.
Dans Xcode Beta 5, une autre façon de faire est d'utiliser @State dans la vue qui lance le modal et d'ajouter une liaison dans la vue modale pour contrôler la visibilité du modal. Cela ne vous oblige pas à accéder à la variable @Environment presentationMode.
struct MyView : View { @State var modalIsPresented = false var body: some View { Button(action: {self.modalIsPresented = true}) { Text("Launch modal view") } .sheet(isPresented: $modalIsPresented, content: { MyModalView(isPresented: self.$modalIsPresented) }) } } struct MyModalView : View { @Binding var isPresented: Bool var body: some View { Button(action: {self.isPresented = false}) { Text("Close modal view") } } }
Félicitations pour avoir adhéré aux principes de SwiftUI avec l'approche déclarative et la source unique de vérité
Cela ne fonctionne que la première fois, si je ferme et réessaye d'ouvrir la fenêtre, cela ne fonctionne plus.
Cela semble fonctionner correctement pour moi, peut-être que vous changez la valeur isPresented ailleurs? Par exemple, si vous rejetez le modal en tirant vers le bas, swiftUI bascule automatiquement la valeur. Au lieu de définir la valeur explicitement sur true / false, essayez d'utiliser isPresented.toggle () à la place
Si je crée un projet avec seulement ces deux vues, cela fonctionne bien. Mais dans mon application réelle, cela ne fonctionne pas. J'ai le même comportement que Mario voit. Je ne place pas isPresented nulle part ailleurs.
@ stardust4891 Pourquoi est-ce la bonne réponse? Quel est le problème avec les autres?
Je suis d'accord avec @ stardust4891, c'est dommage que le mode de présentation ait reçu plus de votes positifs pour sa réponse. Lorsque vous consultez la documentation officielle sur wrappedValue, voici ce qu'Apple a écrit: "Cette propriété fournit un accès principal aux données de la valeur. Cependant, vous n'accédez pas directement à wrappedValue. Au lieu de cela, vous utilisez la variable de propriété créée avec \ @Binding attribut." Cela stimule l'utilisation de liaisons comme indiqué dans cette réponse. Pour une seule source de vérité.
Ecrire votre propre @Binding var isPresented: Bool
est l'option la plus flexible. Il peut même être déclaré dans votre VM en tant que @Published
, au lieu de @State
comme ci-dessus. Je présentais un modal avec un NavigationView, que je voulais être en mesure de rejeter le modal entier sur n'importe quel "bouton Done" de NavigationLink. L'utilisation de presentationMode m'a obligé à suivre plus d'état que nécessaire. Mais le simple fait de me lier à ma vm m'a permis de isPresented
facilement le modal de n'importe quel bouton Terminé, en basculant simplement isPresented
sur false.
Si quelqu'un se demande comment faire fonctionner un aperçu pour la vue modale ci-dessus: utilisez .constant
pour créer une liaison avec une valeur immuable, comme celle-ci MyModalView(isPresented: .constant(true))
Dans Xcode 11.0 beta 7, la valeur est maintenant encapsulée, la fonction suivante fonctionne pour moi:
func dismiss() { self.presentationMode.wrappedValue.dismiss() }
Il semble que pour Xcode 11 Beta 7 (c'est sur la build 11M392r de Xcode), c'est légèrement différent.
@Environment(\.presentationMode) var presentation Button(action: { self.presentation.wrappedValue.dismiss() }) { Text("Dismiss") }
C'est faux. Vous devriez passer une variable d'état qui est également utilisée pour isPresented, plutôt que de jouer avec le presentationMode.
Les vues modales dans SwiftUI semblent simples jusqu'à ce que vous commenciez à les utiliser dans une vue List
ou Form
. J'ai créé une petite bibliothèque qui englobe tous les cas de bord et rend l'utilisation des vues modales identique à la paire NavigationView
- NavigationLink
.
La bibliothèque est open-source ici: https://github.com/diniska/modal-view . Vous pouvez l'inclure dans le projet à l'aide de Swift Package Manager, ou simplement en copiant le fichier unique inclus dans la bibliothèque.
La solution pour votre code serait:
struct DetailView: View { var dismiss: () -> () var body: some View { Text("Detail") Button(action: dismiss) { Text("Click to dismiss") } } } struct ContentView : View { var body: some View { ModalPresenter { ModalLink(destination: DetailView.init(dismiss:)) { Text("Click to show") } } } }
De plus, il existe un article avec une description complète et des exemples: Comment présenter la vue modale dans SwiftUI
Utilisez la variable d'environnement dans PresentationMode. Ce lien GitHub vous aidera peut-être à résoudre le problème https://github.com/MannaICT13/Sheet-in-SwiftUI
C'est une solution simple:
struct ContentView2 : View { @Environment (\.presentationMode) var presentationMode var body : some View { VStack { Text("This is ContentView2") Button(action: { self.presentationMode.wrappedValue.dismiss() }, label: { Text("Back") }) } } } struct ContentView: View { @State var isShowingSheet : Bool = false var body: some View { Button(action: { self.isShowingSheet.toggle() }, label: { Text("Click Here") }).sheet(isPresented: $isShowingSheet, content: { ContentView2() }) } }
Une façon de procéder peut être de déclarer votre propre modificateur pour la présentation modale et le rejet.
struct ContentView: View { @State var showModal = false var body: some View { Text("Show").foregroundColor(.blue).onTapGesture { withAnimation(.easeIn(duration: 0.75)) { self.showModal = true } }.showModal($showModal, { Text("Dismiss").foregroundColor(.blue).onTapGesture { withAnimation(.easeIn(duration: 0.75)) { self.showModal = false } } }) } }
Ensuite, vous pouvez utiliser le modificateur sur n'importe quelle vue pour laquelle vous souhaitez indiquer comment afficher une vue et fermer cette vue. Tout comme un popover ou un modificateur de feuille.
extension View { func showModal<T>(_ binding: Binding<Bool>, _ view: @escaping () -> T) -> some View where T: View { let windowHeightOffset = (UIApplication.shared.windows.first?.frame.height ?? 600) * -1 return ZStack { self view().frame(maxWidth: .infinity, maxHeight: .infinity).edgesIgnoringSafeArea(.all).offset(x: 0, y: binding.wrappedValue ? 0 : windowHeightOffset) } } }
La présentation est en plein écran à partir du haut, si vous souhaitez qu'elle vienne du côté, changez la transition à l'intérieur du modificateur en début ou en fin. D'autres transitions fonctionneraient également, comme l'opacité ou l'échelle.
Oui, mon ancienne application est tombée en panne, je mettrai à jour quand je trouverai une solution. Désolé Andrew
% câlins%% câlins%% câlins%
(l'exemple de code ne fonctionne pas avec swift 1, mais vous pouvez toujours l'essayer sans le bloc @main
)
Exemple d'application complet pour l'utilisation de feuilles:
struct SheetLink<Content> : View where Content: View { @State var text: String @State var displaySheet = false @State var content: Content var body: some View { HStack { Button(text, action: { self.displaySheet = true } ).buttonStyle(PlainButtonStyle()).foregroundColor(.blue) } .sheet(isPresented: $displaySheet) { SheetTemplateView(isPresented: self.$displaySheet, content: content) } } } struct SheetTemplateView<Content> : View where Content: View { @Binding var isPresented: Bool @State var content: Content var body: some View { VStack{ HStack{ Button("Back!", action: { isPresented.toggle() } ).buttonStyle(PlainButtonStyle()).foregroundColor(.blue) Spacer() } Spacer() content Spacer() } .padding() } }
et lorsque la sous-vue est plus grande que la vue principale:
Et le code derrière ceci:
@main struct TestAppApp: App { var body: some Scene { WindowGroup { SheetLink(text: "click me!", content: ChildView() ) .padding(.all, 100) } } } struct ChildView: View { var body: some View { Text("this is subView!") } }
Navigation
ou ignorer si Modal
Il suffit de prendre le presentationMode
de l'environnement dans la vue destination et dismiss
le wrappedValue
de celui - ci:
struct DestinationView: View { @Environment(\.presentationMode) private var presentationMode var body: some View { Button("Dismiss") { self.presentationMode.wrappedValue.dismiss() } } }
Merci d'avoir posté ceci. C'est pourquoi PresentationMode
n'est probablement pas la meilleure solution pour ignorer un modal, car il peut à la place apparaître dans la vue précédente si vous avez un NavigationView
. Si vous voulez vous assurer de rejeter un modal, vous devez passer une variable @State
.
Aucun des exemples que j'ai vus n'a de méthode pour rejeter une vue présentée, donc je ne pense pas qu'il y en ait encore une.
Je suis presque sûr qu'ils l'introduiront avec la prochaine version bêta. La méthode Pop est également absente.
Je pense qu'il est important de se rappeler que SwiftUI est un changement de paradigme. Nous devons penser davantage en termes d '"état" et moins en termes d'écriture d'instructions conditionnelles, etc. Donc, comme d'autres l'ont écrit, il s'agit plus d'écouter l'état via
@Environment
ou@State
ou d'autres "Property Wrappers". Il s'agit d'un passage au modèle d'observateur dans un cadre déclaratif, pour ceux qui aiment les phrases compliquées :-)Il existe maintenant un moyen très propre de le faire dans la bêta 5. Voir ma réponse ci-dessous. BTW, la même méthode fonctionne pour faire apparaître une vue de navigation.