Image de présentation d’un écran iOS illustrant les shapes en SwiftUI, plusieurs forme de différentes couleurs sont présentes

Modal

Une modale est une vue qui s’affiche par-dessus une autre et sert généralement à afficher du contenu temporaire ou une action secondaire. Son objectif est de diriger l’utilisateur vers une vue où il doit effectuer une action spécifique. Une fois cette action terminée, il revient automatiquement à la navigation courante sur laquelle il se trouvait.

 

Créer une modale (.sheet)

Pour créer une modale, on utilise le modificateur .sheet() sur la vue principale, ici une ZStack. Ce modificateur prend en paramètre une valeur booléenne (isPresented), qui détermine si la modale doit être affichée ou non.

Par défaut, cette valeur est définie à false, ce qui signifie que la modale n’est pas visible. Pour l’afficher, on utilise un élément interactif comme un bouton. Lorsque l’utilisateur appuie sur ce bouton, la valeur de isPresented passe à true, ce qui déclenche l’affichage de la modale.

Dans cet exemple, la modale affiche la vue CvView, qui vient se superposer à la vue actuelle. L’utilisateur peut ensuite fermer la modale en effectuant un glissement vers le bas, ce qui rétablit la navigation normale.


struct DownloadCvView: View {
    @State private var isModalPresented = false

    var body: some View {
        ZStack {
            Color.yellow.edgesIgnoringSafeArea(.all)
            VStack {
                Button(action: {
              
                        isModalPresented = true
                    
                }) {
                    Text("Télécharger le CV")
                        .font(.headline)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color.white)
                        .foregroundStyle(.black)
                        .cornerRadius(10)
                        .shadow(radius: 5)
                   
                }
                .padding(.horizontal, 20)

            }
        }
        .sheet(isPresented: $isModalPresented) {
            CvView()
        }
    }
}

struct CvView: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("Téléchargement en cours...")
                .font(.largeTitle.bold())
                .foregroundStyle(.black)
                .padding(.top, 20)
                
            Image(systemName: "doc.circle.fill")
                .font(.system(size: 80))
                .foregroundStyle(.yellow)
                .padding()
                
            Text("Merci de patienter pendant le téléchargement du CV du candidat.")
                .font(.body)
                .foregroundStyle(.gray)
                .multilineTextAlignment(.center)
                .padding(.horizontal, 20)
            
            Spacer()
        }
        .frame(maxWidth: .infinity)
        .cornerRadius(20)
        .padding(.horizontal, 20)
        .padding(.bottom, 30)
    }
}

Comment fermer une modale ?

Par défaut, il est possible de fermer une modale en la glissant vers le bas. Cependant, il existe également une autre méthode en utilisant le property wrapper @Environment, qui permet d’accéder à certains outils liés à l’environnement de l’application.

Dans ce cas, on utilise @Environment(\.dismiss), qui permet d’observer et de fermer la vue actuelle lorsque l’utilisateur déclenche une action. Cette approche est particulièrement utile lorsque l’on souhaite contrôler explicitement la fermeture de la modale, par exemple via un bouton “Fermer”.

Pour fermer la vue, il suffit d’appeler la propriété dismiss() dans l’action du bouton, et la modale se ferme instantanément. Nous reviendrons plus en détail sur l’utilisation de @Environment et ses autres possibilités dans la prochaine formation.


struct CvView: View {
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Téléchargement en cours...")
                .font(.largeTitle.bold())
                .foregroundStyle(.black)
                .padding(.top, 20)
                
            Image(systemName: "doc.circle.fill")
                .font(.system(size: 80))
                .foregroundStyle(.yellow)
                .padding()
                
            Text("Merci de patienter pendant le téléchargement du CV du candidat.")
                .font(.body)
                .foregroundStyle(.gray)
                .multilineTextAlignment(.center)
                .padding(.horizontal, 20)
            Button(action: {
                dismiss()
            }) {
                HStack {
                    Image(systemName: "xmark.circle.fill")
                        .font(.title2)
                    Text("Fermer")
                        .font(.headline)
                        .bold()
                }
                .padding()
                .frame(maxWidth: .infinity)
                .background(Color.red.opacity(0.8))
                .foregroundStyle(.white)
                .cornerRadius(10)
                .shadow(radius: 5)
            }
            .padding(.horizontal, 20)
            .padding(.bottom, 10)
        }
        .frame(maxWidth: .infinity)
        .background(Color.white)
        .cornerRadius(20)
        .padding(.horizontal, 20)
        .padding(.bottom, 30)
    }
}

Passer des données à une modale

Si tu as saisi la transition de données dans le cours précédent avec le composant NavigationLink, c’est exactement le même principe ici.

Tu as une donnée à envoyer à la vue modale, et cette dernière doit s’attendre à recevoir ce type de donnée via une propriété du même type. Lorsque tu instancies la modale, tu lui transmets la donnée, et la modale la récupère correctement pour l’afficher.


struct DownloadCvView: View {
    @State private var isModalPresented = false
    @State private var candidateName = "Frank Castle"
    
    var body: some View {
        ZStack {
            Color.yellow.edgesIgnoringSafeArea(.all)
            VStack {
                Button(action: {
              
                        isModalPresented = true
                    
                }) {
                    Text("Télécharger le CV de ***\(candidateName)***")
                        .font(.headline)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color.white)
                        .foregroundStyle(.black)
                        .cornerRadius(10)
                        .shadow(radius: 5)
                   
                }
                .padding(.horizontal, 20)

            }
        }
        .sheet(isPresented: $isModalPresented) {
            CvView(candidateName: candidateName)
        }
    }
}

struct CvView: View {
    let candidateName: String
    var body: some View {
        VStack(spacing: 20) {
            Text("Téléchargement en cours...")
                .font(.largeTitle.bold())
                .foregroundStyle(.black)
                .padding(.top, 20)
                
            Image(systemName: "doc.circle.fill")
                .font(.system(size: 80))
                .foregroundStyle(.yellow)
                .padding()
                
            Text("Merci de patienter pendant le téléchargement du CV de ***\(candidateName)***.")
                            .font(.body)
                            .foregroundStyle(.gray)
                            .multilineTextAlignment(.center)
                            .padding(.horizontal, 20)
        }
        .frame(maxWidth: .infinity)
        .background(Color.white)
        .cornerRadius(20)
        .padding(.horizontal, 20)
        .padding(.bottom, 30)
    }
}

Customiser une modale

Il existe plusieurs modificateurs pour .sheet() qui te permettent de gérer son apparence et son comportement, comme la hauteur d’affichage, la présence ou non de la barre de sélection, le rayon des coins (cornerRadius), la couleur de fond, etc.

Je te mets ci-dessous un tableau récapitulatif de tous les modificateurs disponibles pour une modale (.sheet()), juste après l’exemple.


struct DownloadCvView: View {
    @State private var isModalPresented = false
    @State private var candidateName = "Frank Castle"

    var body: some View {
        ZStack {
            Color.yellow.edgesIgnoringSafeArea(.all)
            VStack(spacing: 20) {
                Button(action: {
                        isModalPresented = true
                }) {
                    Text("Télécharger le CV de \(candidateName)")
                        .font(.headline)
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color.white)
                        .foregroundStyle(.black)
                        .cornerRadius(8)
                        .shadow(radius: 5)
                }
                .padding(.horizontal, 20)
            }
        }
        .sheet(isPresented: $isModalPresented) {
            CvView(candidateName: candidateName)
                .presentationDetents([.fraction(0.2)])
                .presentationDragIndicator(.visible)
                .presentationBackground(Color.black.opacity(0.3))
                .presentationCornerRadius(8)
        }
    }
}

Modificateur Fonction

.presentationDetents([.medium, .large])

Définit la hauteur de la modale

.presentationDragIndicator(.visible)

Affiche ou masque l’indicateur de glissement

.presentationBackground(Color.teal)

Change la couleur de fond de la modale

.presentationBackground(Image(« backgroundImage »))

Ajoute une image en fond

.interactiveDismissDisabled(true)

 

Empêche la fermeture de la modale par swipe

.presentationCornerRadius(20) (iOS 17+)

 

Change le rayon des coins de la modale

.presentationBackgroundInteraction(.enabled)

Permet d’interagir avec l’écran derrière la modale

Format Pop-Up .popover()

La .sheet() présente une vue modale qui apparaît par défaut depuis le bas de l’écran, et ce comportement n’est pas modifiable.

Si tu souhaites créer une pop-up qui s’affiche au centre de l’écran, tu peux utiliser le modificateur .popover(), qui fonctionne de la même manière que .sheet(), mais permet d’afficher la vue au centre plutôt qu’en bas.


struct ContentView: View {
    @State private var isPopoverPresented = false

    var body: some View {
        VStack {
            Button("Afficher la Pop-up") {
                isPopoverPresented = true
            }
            .popover(isPresented: $isPopoverPresented) {
                VStack {
                    Text("Ceci est une pop-up")
                        .font(.headline)
                        .foregroundStyle(.black)
                        .padding()

                    Button("Fermer") {
                        isPopoverPresented = false
                    }
                    .padding()
                }
                .frame(width: 300, height: 200)
                .background(Color.white)
                .cornerRadius(10)
                .shadow(radius: 10)
                .presentationCompactAdaptation(.popover)
            }
        }
    }
}

Modificateur

Description

.popoverAttachmentAnchor(.point(.bottom))

 

Définit où la pop-up est attachée

.popoverArrowEdge(.top)

Définit la position de la flèche de la pop-up

.presentationCompactAdaptation(.popover)

Force l’affichage en mode popover sur iPhone

Pop-Up Custom

Enfin, si la vue proposée par .popover() ne convient pas, tu peux créer ta propre modale personnalisée, en la conditionnant comme pour les autres cas (.sheet() ou .popover()). Pour ça, la vue modale custom devra simplement avoir un frame plus petit que son parent et être affichée au-dessus de l’écran principal via un ZStack. Ça permet d’adapter entièrement son apparence et son comportement selon les besoins de ton application.

struct PopupExampleView: View {
    @State private var isPopupPresented = false
    
    var body: some View {
        ZStack {
            VStack {
                Button(action: {
                        isPopupPresented = true
                }) {
                    Text("Télécharger")
                        .font(.system(size: 18, weight: .medium))
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color.black)
                        .foregroundStyle(.white)
                        .cornerRadius(8)
                }
                .padding(.horizontal, 20)
            }
            
            if isPopupPresented {
                ZStack {
                    Color.black.opacity(0.3)
                        .edgesIgnoringSafeArea(.all)
                    VStack(spacing: 20) {
                        Text("Téléchargement en cours...")
                            .font(.system(size: 18, weight: .medium))
                            .foregroundStyle(.black)
                        
                        Image(systemName: "doc.circle.fill")
                            .font(.system(size: 50))
                            .foregroundStyle(.black)
                        
                        Button(action: {
                            withAnimation {
                                isPopupPresented = false
                            }
                        }) {
                            Text("Fermer")
                                .font(.system(size: 18, weight: .medium))
                                .padding()
                                .frame(maxWidth: .infinity)
                                .background(Color.black)
                                .foregroundStyle(.white)
                                .cornerRadius(8)
                                .shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)
                        }
                    }
                    .padding()
                    .frame(width: 300, height: 200)
                    .background(Color.white)
                    .cornerRadius(12)
                }
            }
        }
    }
}

 Composant Alert

L’Alert est un composant natif qui permet d’afficher une fenêtre pop-up modale pour prévenir l’utilisateur, lui demander une confirmation ou lui proposer des choix.


struct AlertView: View {
    @State private var showAlert = false

    var body: some View {
        VStack {
            Button("Afficher l'alerte") {
                showAlert = true
            }
        }
        .alert("Attention", isPresented: $showAlert) {
            Button("OK", role: .cancel) { }
        } message: {
            Text("Ceci est un message d’alerte.")
        }
    }
}

 Ajouter plusieurs boutons dans une Alert

On peut ajouter plusieurs boutons dans une Alert, chacun avec un rôle spécifique (.destructive, .cancel, etc.). Ces rôles améliorent l’expérience utilisateur en appliquant automatiquement un style visuel adapté à certains boutons (par exemple, un bouton .destructive sera affiché en rouge pour signaler une action dangereuse).

Une question que l’on peut se poser est : est-il possible de personnaliser le design d’une Alert ? La réponse est non. SwiftUI ne permet pas de modifier l’apparence d’une Alert (couleur de fond, police, style des boutons).

Si tu veux une alerte avec un design sur mesure, tu peux la recréer à l’aide d’une sheet, en affichant une vue personnalisée.


struct AlertView: View {
    @State private var showDeleteAlert = false

    var body: some View {
        VStack {
            Button("Supprimer l'élément") {
                showDeleteAlert = true
            }
        }
        .alert("Confirmer la suppression", isPresented: $showDeleteAlert) {
            Button("Annuler", role: .cancel) { }
            Button("Supprimer", role: .destructive) {
                print("Élément supprimé")
            }
        } message: {
            Text("Cette action est irréversible.")
        }
    }
}

Tips

Aujourd’hui, j’ai ma première panne d’inspiration pour les trucs et astuces… alors j’en profite pour te partager un outil que j’utilise beaucoup : Remove.bg.

Ce site en ligne permet de retirer le fond d’une image en un clic.

Voilà, c’est cadeau !

Tips

Aujourd’hui, j’ai ma première panne d’inspiration pour les trucs et astuces… alors j’en profite pour te partager un outil que j’utilise beaucoup : Remove.bg.

Ce site en ligne permet de retirer le fond d’une image en un clic.

Voilà, c’est cadeau !