Développement Mobile

Construire sa première vue SwiftUI : views, modifiers et state

11 دقائق للقراءة

Le premier contact avec SwiftUI déroute parfois : on n’écrit plus une séquence d’instructions qui modifie une hiérarchie de vues, on décrit la vue en fonction de l’état courant. Ce changement de paradigme demande quelques exemples concrets pour s’ancrer. Ce tutoriel part d’un projet vide et construit une vue SwiftUI complète — un mini-formulaire de profil avec champs, slider, bouton et liste — en introduisant chaque concept clé au moment où il sert.

📘 Guide principal de la série : Développer une application iOS avec Swift et SwiftUI : panorama 2026. Avant d’attaquer ce tutoriel, l’environnement Xcode doit être opérationnel — voir le tutoriel d’installation si nécessaire.

Prérequis

  • Xcode 26 installé sur macOS Tahoe.
  • Notions de base en Swift (variables, fonctions, structs) — voir le tutoriel sur les bases du langage.
  • Temps estimé : 60 à 90 minutes.
  • Niveau : débutant.

Étape 1 — Créer un projet iOS SwiftUI

Le template iOS App de Xcode produit en deux clics un projet SwiftUI fonctionnel. Cette étape génère la structure minimale et permet d’expérimenter immédiatement.

Dans Xcode : File > New > Project, choisir iOS > App, cliquer Next. Remplir :

  • Product Name : ProfilSwiftUI
  • Team : votre Apple ID
  • Organization Identifier : com.example
  • Interface : SwiftUI
  • Language : Swift
  • Storage : None

Sauvegarder dans ~/Developer. Xcode ouvre le projet sur ContentView.swift qui contient déjà un VStack avec une icône globe et le texte « Hello, world! ». Cette vue est notre point de départ.

Étape 2 — Comprendre l’anatomie d’une View

En SwiftUI, une vue est une struct qui implémente le protocole View. Ce protocole exige une seule chose : une propriété calculée body qui retourne une autre View. La composition par imbrication produit toute la hiérarchie graphique.

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 16) {
            Image(systemName: "person.circle.fill")
                .font(.system(size: 80))
                .foregroundStyle(.blue)
            Text("Profil")
                .font(.title)
                .bold()
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

Le mot-clé some View est un type opaque qui dit au compilateur « cette propriété retourne un type concret qui conforme à View, mais l’appelant n’a pas besoin de le connaître ». Cette astuce permet à SwiftUI de fonctionner avec des types très complexes sans imposer leur écriture explicite. Le bloc #Preview (macro depuis Xcode 15) déclare un aperçu live qui s’affiche dans le canvas à droite — appuyer sur ⌥⌘P pour le réafficher s’il est masqué.

Étape 3 — Ajouter du state local avec @State

Pour qu’une vue réagisse à des modifications de données, ces données doivent être marquées comme partie de l’état observé. Le wrapper @State indique à SwiftUI qu’une propriété fait partie de la source de vérité locale de la vue et déclenche un re-rendu à chaque modification.

struct ContentView: View {
    @State private var nom: String = ""
    @State private var age: Double = 25

    var body: some View {
        VStack(spacing: 24) {
            Text("Profil de \(nom.isEmpty ? "…" : nom)")
                .font(.title2)

            TextField("Votre prénom", text: $nom)
                .textFieldStyle(.roundedBorder)

            HStack {
                Text("Âge : \(Int(age))")
                Slider(value: $age, in: 18...80)
            }
        }
        .padding()
    }
}

Le préfixe $ devant nom et age crée un binding : une référence bidirectionnelle qui permet au TextField et au Slider de lire et modifier la propriété. Sans le $, on aurait juste la valeur. Cette distinction est centrale dans SwiftUI : nom lit la valeur, $nom donne accès au lien d’écriture. À l’exécution, taper dans le champ texte met à jour le titre en temps réel, et bouger le slider met à jour le label d’âge.

Étape 4 — Modifiers et composition

Un modifier est une méthode qui retourne une nouvelle vue dérivée. .padding(), .font(.title), .foregroundStyle(.red) sont des modifiers. L’ordre dans lequel on les applique compte parfois : .padding().background(.yellow) peint un fond derrière les marges, alors que .background(.yellow).padding() peint un fond seulement derrière le contenu.

Text("Bonjour")
    .font(.title)
    .foregroundStyle(.white)
    .padding(.horizontal, 24)
    .padding(.vertical, 12)
    .background(.blue, in: Capsule())
    .shadow(radius: 4)

Ce snippet produit un texte blanc sur un fond bleu en forme de capsule, avec une ombre douce et des marges intérieures asymétriques. La forme .background(.blue, in: Capsule()) est plus directe que la version équivalente avec ZStack. Au survol de l’aperçu Xcode, en mode Selectable, on peut cliquer sur chaque morceau pour voir le code correspondant — une aide d’apprentissage très efficace.

Étape 5 — Disposition : VStack, HStack, ZStack et Grid

SwiftUI fournit quatre containers principaux. VStack empile verticalement, HStack horizontalement, ZStack superpose en profondeur. Grid (depuis iOS 16) produit une grille alignée comme un tableau. Le paramètre spacing: contrôle l’espace inter-éléments, et alignment: contrôle l’alignement perpendiculaire à l’axe.

VStack(alignment: .leading, spacing: 8) {
    Text("Nom").bold()
    Text("Aïssatou Sow")
    Divider()
    HStack(spacing: 12) {
        Image(systemName: "envelope")
        Text("contact@example.com")
    }
    .foregroundStyle(.secondary)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))

maxWidth: .infinity demande à la vue de prendre toute la largeur disponible. Le modifier .background(.regularMaterial, …) utilise un matériau translucide qui s’adapte au thème clair/sombre. Cette mise en page produit une carte d’identité visuellement homogène en quelques lignes — résultat équivalent à plusieurs dizaines de lignes en UIKit avec contraintes manuelles.

Étape 6 — Listes dynamiques avec ForEach et List

Pour afficher une collection, List est le bon outil : il gère le scroll, le styling natif, les séparateurs et les actions de swipe. Pour itérer sur un tableau, ForEach est l’équivalent fonctionnel d’une boucle.

struct Hobby: Identifiable {
    let id = UUID()
    let nom: String
    let icone: String
}

struct ContentView: View {
    @State private var hobbies: [Hobby] = [
        Hobby(nom: "Lecture", icone: "book.fill"),
        Hobby(nom: "Cyclisme", icone: "bicycle"),
        Hobby(nom: "Photographie", icone: "camera.fill")
    ]

    var body: some View {
        List {
            ForEach(hobbies) { h in
                HStack {
                    Image(systemName: h.icone)
                        .foregroundStyle(.blue)
                    Text(h.nom)
                }
            }
            .onDelete { indexSet in
                hobbies.remove(atOffsets: indexSet)
            }
        }
    }
}

Le protocole Identifiable exige un identifiant unique id ; UUID() en génère un nouveau à chaque création. ForEach s’appuie sur cet identifiant pour synchroniser efficacement les changements de liste — quand un élément est retiré, SwiftUI anime la disparition sans recréer toute la liste. Le modifier .onDelete ajoute le geste de swipe-pour-supprimer caractéristique d’iOS, sans aucun code de gestion explicite.

Étape 7 — Boutons et actions

Un Button SwiftUI prend une action et un label. Il gère gratuitement les états (pressé, relâché, désactivé) et l’accessibilité. Pour styliser un bouton, on combine .buttonStyle et les modifiers classiques.

@State private var compteur = 0

Button {
    compteur += 1
} label: {
    HStack {
        Image(systemName: "plus.circle.fill")
        Text("Incrémenter (\(compteur))")
    }
    .padding()
    .frame(maxWidth: .infinity)
    .background(.blue.gradient, in: RoundedRectangle(cornerRadius: 12))
    .foregroundStyle(.white)
}
.disabled(compteur >= 10)

Le modifier .disabled(compteur >= 10) grise le bouton et empêche les clics quand la condition est vraie. SwiftUI applique automatiquement un style visuel d’état désactivé sans code explicite. La syntaxe avec deux trailing closures ({ … } label: { … }) est une forme officielle introduite avec Swift 5.3 et reste la plus lisible pour les boutons riches.

Étape 8 — Passer un binding à une sous-vue

Quand une vue grossit, on la découpe en sous-vues. Si une sous-vue doit lire et modifier un état de la vue parente, elle reçoit un @Binding plutôt qu’une valeur statique. C’est la mécanique de communication montante dans SwiftUI.

struct SliderAvecLabel: View {
    let titre: String
    @Binding var valeur: Double

    var body: some View {
        VStack(alignment: .leading) {
            Text("\(titre) : \(Int(valeur))")
                .font(.subheadline)
            Slider(value: $valeur, in: 0...100)
        }
    }
}

struct ContentView: View {
    @State private var luminosite: Double = 50

    var body: some View {
        SliderAvecLabel(titre: "Luminosité", valeur: $luminosite)
            .padding()
    }
}

La parent passe $luminosite au paramètre valeur annoté @Binding. La sous-vue lit et écrit dans la même source de vérité, sans avoir besoin de connaître la classe parente ni d’utiliser des delegates. Ce pattern remplace les delegates et callbacks classiques de UIKit dans la grande majorité des cas.

Étape 9 — Tester sur le simulateur

Avant de passer à l’écran suivant ou au prochain tutoriel, exécuter la vue sur un simulateur réel vérifie qu’aucun bug ne se cache derrière la magie de l’aperçu. Sélectionner un simulateur dans la barre supérieure de Xcode (par exemple iPhone 16 Pro), puis cliquer le bouton triangle ou appuyer ⌘R.

Au lancement, l’app affiche le profil construit étape après étape. Taper dans le champ texte met à jour le titre, bouger le slider met à jour le label, supprimer un hobby de la liste fait disparaître la ligne avec animation. Si tout fonctionne, l’environnement complet — code, aperçu et runtime — est validé.

Aperçu rapide des modifiers les plus utilisés au quotidien

Une fois la mécanique de base assimilée, certains modifiers reviennent dans la quasi-totalité des vues qu’on écrit. Les recenser ensemble aide à constituer un vocabulaire opérationnel rapide. .padding() ajoute des marges intérieures et accepte un côté précis (.horizontal, .top) ou une valeur numérique (16 par exemple). .frame(width:height:) impose une dimension fixe ; .frame(maxWidth: .infinity) demande à occuper toute la largeur disponible. .foregroundStyle colore le contenu et a remplacé l’ancien .foregroundColor en iOS 17 — accepter des gradients et matériaux en plus des couleurs unies est l’un des gains principaux. .background peint un fond derrière la vue, avec une forme optionnelle pour produire des cartes ou capsules. .cornerRadius arrondit les coins ; la forme moderne .clipShape(RoundedRectangle(cornerRadius: 12)) est plus flexible. .shadow projette une ombre, paramétrable en rayon et en décalage.

Au-delà du style, deux modifiers reviennent en permanence pour l’interaction et l’accessibilité. .onTapGesture capture un tap simple, .onLongPressGesture un appui long. .accessibilityLabel("Description claire") remplace l’auto-description par un texte explicite pour VoiceOver — geste qui prend dix secondes et change radicalement l’utilisabilité pour les personnes non-voyantes. La discipline « un modifier d’accessibilité par interaction non triviale » est ce qui sépare une app vite faite d’une app sérieuse.

Erreurs fréquentes en SwiftUI

Erreur Cause Solution
« Cannot find ‘$nom’ in scope » Tentative d’utiliser $ sur une propriété non-@State Annoter la propriété @State, @Binding, ou utiliser @Observable
L’aperçu ne se met pas à jour Erreur de compilation silencieuse Vérifier la zone Issues (⇧⌘5) ; redémarrer l’aperçu avec ⌥⌘P
« Type ‘X’ does not conform to protocol ‘Identifiable’ » Élément de ForEach sans id Conformer le type à Identifiable ou passer id: \\.self à ForEach
Le clavier cache le champ TextField Pas de remontée automatique Encapsuler dans ScrollView ou utiliser .scrollDismissesKeyboard(.interactively)
« The compiler is unable to type-check this expression in reasonable time » Body trop complexe en une seule expression Découper en sous-vues ou en propriétés calculées
Animation manquante Modification d’état hors d’un bloc withAnimation Envelopper la modification : withAnimation { compteur += 1 }

Foire aux questions

Pourquoi body retourne some View et pas View ?

Le protocole View a une associated type (Body) qui le rend inutilisable comme type concret de retour. some View est un type opaque : il dit « un type concret précis qui conforme à View, masqué à l’appelant ». Cela permet à SwiftUI de produire des types très complexes en interne sans imposer leur écriture.

Quand utiliser @State, @Binding, @Observable ?

@State pour un état local à une vue. @Binding pour qu’une sous-vue lise et écrive un état du parent. @Observable pour un modèle partagé entre plusieurs vues. Le tutoriel Gérer l’état d’une app avec @Observable détaille le troisième cas.

L’aperçu Xcode est-il une simulation fidèle ?

Très fidèle pour la mise en page et le rendu visuel. Moins fidèle pour les comportements asynchrones, les notifications système, et les animations dépendant du frame rate. Tester sur un simulateur ou un appareil réel reste nécessaire avant de considérer une fonctionnalité terminée.

Que faire quand un modifier ne fait rien ?

Trois pistes : vérifier l’ordre des modifiers (ils s’appliquent dans l’ordre de chaînage) ; vérifier qu’on les applique à la bonne vue (un modifier appliqué à un container affecte le container, pas ses enfants) ; consulter la documentation officielle du modifier — certains ont des effets contextuels (.foregroundStyle sur List ne colore pas tous les enfants).

Peut-on mélanger UIKit et SwiftUI ?

Oui, avec UIViewRepresentable et UIViewControllerRepresentable pour embarquer du UIKit dans SwiftUI, ou UIHostingController pour embarquer du SwiftUI dans UIKit. Cette interop est largement utilisée pour migrer progressivement une app legacy.

SwiftUI fonctionne-t-il sur iPad et Mac ?

Oui, le même code s’exécute sur iPhone, iPad, Mac, Apple Watch, Apple TV et Apple Vision Pro avec des adaptations mineures (NavigationSplitView pour les grands écrans, modifiers conditionnels par plateforme). C’est une des forces de SwiftUI sur UIKit.

Tutoriels suivants conseillés

Ressources officielles

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité