# SwiftUI : Construire des interfaces modernes pour iOS > Apprenez à créer des interfaces utilisateur modernes avec SwiftUI : syntaxe déclarative, composants, animations et bonnes pratiques pour iOS 18. - Published: 2026-01-05 - Updated: 2026-03-28 - Author: SharpSkill - Tags: swiftui, ios, swift, ui, apple - Reading time: 12 min --- SwiftUI a transformé le développement d'interfaces sur les plateformes Apple. Avec sa syntaxe déclarative et son intégration native, ce framework permet de créer des applications élégantes en moins de lignes de code que jamais. iOS 18 apporte des améliorations majeures en termes de performance et de nouvelles fonctionnalités. > **Pourquoi SwiftUI en 2026 ?** > > SwiftUI est désormais mature avec iOS 18, offrant un rendu optimisé, une meilleure gestion mémoire, et une intégration UIKit simplifiée. C'est le standard pour les nouvelles applications iOS. ## Comprendre le paradigme déclaratif Avant de coder, comprenons ce qui rend SwiftUI différent. Avec UIKit (l'ancien framework), vous deviez dire à iOS **comment** construire l'interface étape par étape : "crée un label, positionne-le ici, change sa couleur quand l'utilisateur clique". C'est le paradigme **impératif**. SwiftUI fonctionne différemment : vous décrivez **ce que** vous voulez afficher, et le framework se charge du reste. C'est comme la différence entre donner des instructions GPS tour par tour (impératif) et simplement indiquer la destination (déclaratif). ### Votre première View En SwiftUI, tout élément d'interface est une `View`. Une View est une structure qui décrit ce qui doit s'afficher à l'écran. Créons notre premier écran avec un titre, un sous-titre et un bouton : ```swift // ContentView.swift import SwiftUI // Chaque écran est une struct qui implémente le protocole View struct ContentView: View { // La propriété "body" décrit ce que la vue affiche // "some View" signifie "un type de View, mais Swift le déduit automatiquement" var body: some View { // VStack = "Vertical Stack" : empile les éléments verticalement // spacing: 20 = 20 points d'espace entre chaque élément VStack(spacing: 20) { // Un simple texte avec des modificateurs de style Text("Bienvenue sur SharpSkill") .font(.largeTitle) // Grande police titre .fontWeight(.bold) // Texte en gras Text("Préparez vos entretiens iOS") .font(.subheadline) // Police plus petite .foregroundColor(.secondary) // Couleur grise secondaire // Bouton avec une action (closure) et un label Button("Commencer") { print("Bouton cliqué !") } .buttonStyle(.borderedProminent) // Style bouton rempli bleu } .padding() // Ajoute des marges autour du VStack } } ``` Remarquez la structure : on déclare ce qu'on veut (textes, bouton), on les empile verticalement (VStack), et on applique des styles via des **modificateurs** (`.font()`, `.padding()`). > **À retenir** : En SwiftUI, vous ne "créez" pas des objets UI — vous **décrivez** l'interface souhaitée. SwiftUI se charge de créer, mettre à jour et détruire les éléments réels. ### Les modificateurs : transformer vos vues Les modificateurs sont des méthodes qu'on chaîne après une vue pour la transformer. Pensez-y comme des filtres Instagram qu'on applique l'un après l'autre. **L'ordre compte** car chaque modificateur crée une nouvelle vue qui enveloppe la précédente. Voici un exemple qui illustre pourquoi l'ordre est crucial : ```swift // ModifiersOrder.swift // Exemple 1 : padding PUIS background Text("SwiftUI") .padding() // 1. Ajoute 16pt d'espace autour du texte .background(.blue) // 2. Le fond bleu couvre le texte + le padding .foregroundColor(.white) // Résultat : texte blanc sur rectangle bleu avec marges // Exemple 2 : background PUIS padding (ordre inversé !) Text("SwiftUI") .background(.blue) // 1. Fond bleu serré autour du texte uniquement .padding() // 2. Padding transparent autour du fond bleu .foregroundColor(.white) // Résultat : texte blanc sur petit rectangle bleu, entouré d'espace vide ``` La différence ? Dans le premier cas, le padding est "à l'intérieur" du background. Dans le second, il est "à l'extérieur". C'est subtil mais essentiel pour maîtriser les layouts. ## Organiser vos interfaces avec les Stacks SwiftUI propose trois conteneurs principaux pour organiser vos vues. Pensez-y comme des boîtes qui arrangent leur contenu différemment. ### VStack, HStack et ZStack - **VStack** (Vertical Stack) : empile les éléments du haut vers le bas - **HStack** (Horizontal Stack) : aligne les éléments de gauche à droite - **ZStack** (Z-axis Stack) : superpose les éléments les uns sur les autres Construisons une carte de profil utilisateur qui combine ces trois stacks. L'objectif : afficher une photo avec un badge de vérification, puis le nom et le rôle de l'utilisateur. ```swift // ProfileCard.swift struct ProfileCard: View { var body: some View { // HStack principal : photo à gauche, infos à droite HStack(spacing: 16) { // ZStack pour superposer le badge sur la photo ZStack(alignment: .bottomTrailing) { // Image de profil (cercle) Image("avatar") .resizable() .frame(width: 60, height: 60) .clipShape(Circle()) // Badge vert "vérifié" en bas à droite Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .background(Circle().fill(.white)) // Cercle blanc derrière } // VStack pour empiler nom et rôle verticalement VStack(alignment: .leading, spacing: 4) { Text("Marie Dupont") .font(.headline) Text("Développeuse iOS") .font(.subheadline) .foregroundColor(.secondary) } // Spacer pousse tout vers la gauche Spacer() // Chevron à droite pour indiquer que c'est cliquable Image(systemName: "chevron.right") .foregroundColor(.gray) } .padding() .background(Color(.systemGray6)) .cornerRadius(12) } } ``` L'astuce ici est le `Spacer()` : c'est un élément invisible qui prend tout l'espace disponible. Sans lui, les éléments seraient centrés. Avec lui, ils sont poussés vers la gauche et le chevron reste collé à droite. > **Astuce Xcode** > > Utilisez `⌘ + clic` sur n'importe quelle vue dans Xcode pour accéder à l'inspecteur visuel. Vous pouvez ajouter des modificateurs sans taper de code ! ## Gérer l'état avec @State et @Binding La gestion d'état est le cœur de SwiftUI. L'état, c'est toute donnée qui peut changer et qui doit mettre à jour l'interface. Quand l'état change, SwiftUI re-calcule automatiquement les vues affectées. ### @State : l'état local d'une vue `@State` est un property wrapper qui dit à SwiftUI : "surveille cette variable, et rafraîchis la vue quand elle change". C'est parfait pour l'état local d'une seule vue. Créons un compteur interactif pour comprendre le mécanisme : ```swift // CounterView.swift struct CounterView: View { // @State crée une "source de vérité" pour cette vue // private car l'état ne devrait pas être modifié de l'extérieur @State private var count = 0 var body: some View { VStack(spacing: 30) { // Ce Text se met à jour automatiquement quand count change Text("\(count)") .font(.system(size: 72, weight: .bold)) HStack(spacing: 40) { // Bouton décrémente Button(action: { count -= 1 // Modifie l'état → la vue se rafraîchit }) { Image(systemName: "minus.circle.fill") .font(.largeTitle) } // Bouton incrémente Button(action: { count += 1 }) { Image(systemName: "plus.circle.fill") .font(.largeTitle) } } } } } ``` Quand vous appuyez sur un bouton, `count` change. SwiftUI détecte ce changement et re-exécute `body` pour mettre à jour l'affichage. Vous n'avez pas à gérer manuellement la mise à jour du label — c'est automatique. ### @Binding : partager l'état entre vues Parfois, une vue enfant doit modifier l'état d'une vue parente. C'est là qu'intervient `@Binding` : il crée une connexion bidirectionnelle vers un `@State` existant. Prenons un exemple concret : un champ de saisie de nom d'utilisateur qui valide en temps réel. ```swift // UsernameValidation.swift // Vue parente : possède l'état struct SignupForm: View { @State private var username = "" // Source de vérité @State private var isValid = false // État de validation var body: some View { VStack(spacing: 20) { // On passe des BINDINGS (avec $) à la vue enfant UsernameField(username: $username, isValid: $isValid) Button("Créer mon compte") { // Soumettre le formulaire } .disabled(!isValid) // Désactivé si invalide .buttonStyle(.borderedProminent) } .padding() } } ``` La vue enfant reçoit des bindings et peut les modifier : ```swift // Vue enfant : reçoit et modifie l'état via @Binding struct UsernameField: View { @Binding var username: String // Connexion vers le @State parent @Binding var isValid: Bool var body: some View { VStack(alignment: .leading, spacing: 8) { TextField("Nom d'utilisateur", text: $username) .textFieldStyle(.roundedBorder) .onChange(of: username) { oldValue, newValue in // Validation : au moins 3 caractères isValid = newValue.count >= 3 } // Feedback visuel HStack { Image(systemName: isValid ? "checkmark.circle" : "xmark.circle") Text("Minimum 3 caractères") } .font(.caption) .foregroundColor(isValid ? .green : .red) } } } ``` Quand l'utilisateur tape dans le TextField, `username` est modifié via le binding. Le parent voit ce changement et peut l'utiliser. C'est une communication bidirectionnelle propre. > **Attention** > > N'utilisez `@State` que pour l'état simple et local. Pour des données partagées entre plusieurs écrans ou de la logique complexe, préférez `@Observable` (iOS 17+) ou les patterns MVVM. ## Afficher des listes dynamiques Les listes sont omniprésentes dans les applications mobiles. SwiftUI propose `List` pour afficher des collections de données avec un style natif iOS (séparateurs, swipe actions, etc.). ### Créer une liste simple Pour afficher une liste, vous avez besoin de deux choses : des données et un moyen de les identifier. Le protocole `Identifiable` permet à SwiftUI de savoir quelle vue correspond à quelle donnée. Commençons par définir notre modèle de données : ```swift // Models/Interview.swift // Identifiable permet à SwiftUI de tracker chaque élément struct Interview: Identifiable { let id = UUID() // Identifiant unique auto-généré let technology: String let difficulty: String let questionCount: Int } ``` Maintenant, créons la liste. L'idée est d'itérer sur nos données avec `ForEach` et de créer une ligne pour chaque élément : ```swift // InterviewListView.swift struct InterviewListView: View { // Données à afficher (en vrai, ça viendrait d'une API) @State private var interviews = [ Interview(technology: "iOS", difficulty: "Intermédiaire", questionCount: 25), Interview(technology: "Android", difficulty: "Avancé", questionCount: 30), Interview(technology: "React", difficulty: "Débutant", questionCount: 20) ] var body: some View { // NavigationStack active la barre de navigation NavigationStack { List { // ForEach itère sur chaque interview // Grâce à Identifiable, pas besoin de spécifier id: ForEach(interviews) { interview in InterviewRow(interview: interview) } } .navigationTitle("Mes entretiens") } } } ``` Et voici la vue pour chaque ligne, extraite dans son propre composant pour la clarté : ```swift // InterviewRow.swift struct InterviewRow: View { let interview: Interview var body: some View { HStack { // Colonne gauche : titre et sous-titre VStack(alignment: .leading, spacing: 4) { Text(interview.technology) .font(.headline) Text(interview.difficulty) .font(.subheadline) .foregroundColor(.secondary) } Spacer() // Badge avec le nombre de questions Text("\(interview.questionCount) Q") .font(.caption) .fontWeight(.medium) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.blue.opacity(0.1)) .cornerRadius(8) } .padding(.vertical, 4) } } ``` En extrayant `InterviewRow` dans sa propre struct, le code est plus lisible et réutilisable. C'est une bonne pratique SwiftUI. ### Ajouter des actions : suppression et réorganisation Les listes iOS permettent naturellement de supprimer (swipe gauche) et réorganiser (glisser-déposer) les éléments. SwiftUI rend ça trivial avec `.onDelete` et `.onMove` : ```swift // InterviewListView.swift (version améliorée) struct InterviewListView: View { @State private var interviews = [/* ... données ... */] var body: some View { NavigationStack { List { ForEach(interviews) { interview in InterviewRow(interview: interview) } // Swipe pour supprimer .onDelete(perform: deleteInterview) // Glisser-déposer pour réorganiser .onMove(perform: moveInterview) } .navigationTitle("Mes entretiens") .toolbar { // Bouton "Modifier" qui active le mode édition EditButton() } } } // Supprime les éléments aux index spécifiés private func deleteInterview(at offsets: IndexSet) { interviews.remove(atOffsets: offsets) } // Déplace les éléments d'une position à une autre private func moveInterview(from source: IndexSet, to destination: Int) { interviews.move(fromOffsets: source, toOffset: destination) } } ``` Avec seulement 4 lignes de code supplémentaires (`.onDelete`, `.onMove`, `EditButton`, et les deux fonctions), vous avez une liste entièrement interactive. C'est la magie de SwiftUI. ## Animer vos interfaces SwiftUI excelle dans les animations. Contrairement à UIKit où les animations demandaient beaucoup de code, ici tout est déclaratif : vous décrivez l'état final et SwiftUI anime la transition. ### Animations implicites avec withAnimation La façon la plus simple d'animer est d'envelopper un changement d'état dans `withAnimation`. SwiftUI détecte ce qui change et anime automatiquement les propriétés visuelles affectées. ```swift // AnimatedCard.swift struct AnimatedCard: View { @State private var isExpanded = false var body: some View { VStack { RoundedRectangle(cornerRadius: 20) .fill(.blue) // Les dimensions changent selon l'état .frame( width: isExpanded ? 300 : 150, height: isExpanded ? 200 : 100 ) Button(isExpanded ? "Réduire" : "Agrandir") { // withAnimation anime TOUS les changements visuels // qui résultent de ce changement d'état withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) { isExpanded.toggle() } } .padding(.top) } } } ``` Quand vous cliquez sur le bouton, `isExpanded` bascule. Grâce à `withAnimation`, le changement de taille du rectangle est animé avec un effet de ressort (`spring`). Vous n'avez pas à spécifier quoi animer — SwiftUI le déduit. ### Transitions : animer l'apparition/disparition Les transitions définissent comment une vue apparaît ou disparaît. Par défaut, c'est un fondu (`opacity`), mais vous pouvez personnaliser : ```swift // TransitionDemo.swift struct TransitionDemo: View { @State private var showDetails = false var body: some View { VStack(spacing: 20) { Button("Afficher les détails") { withAnimation(.easeInOut(duration: 0.3)) { showDetails.toggle() } } // Cette vue apparaît/disparaît avec une transition if showDetails { DetailCard() // Transition asymétrique : différente à l'entrée et à la sortie .transition( .asymmetric( insertion: .scale.combined(with: .opacity), // Entrée : zoom + fondu removal: .slide // Sortie : glissement ) ) } } } } struct DetailCard: View { var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Détails de l'entretien") .font(.headline) Text("25 questions • 45 minutes") .foregroundColor(.secondary) } .padding() .background(Color(.systemGray6)) .cornerRadius(16) } } ``` Ici, la carte apparaît en zoomant depuis le centre (scale + opacity) et disparaît en glissant sur le côté (slide). Ces micro-animations rendent l'interface vivante et professionnelle. > **Performance** > > SwiftUI optimise automatiquement les animations. Préférez `.spring()` pour un rendu naturel, et évitez les animations trop longues (> 0.5s) qui frustrent les utilisateurs. ## Charger des données asynchrones Dans une vraie application, vos données viennent souvent d'une API. Swift Concurrency (async/await) s'intègre parfaitement avec SwiftUI grâce au modificateur `.task`. ### Le pattern Loading / Error / Success Voici le pattern standard pour afficher des données depuis une API. On gère trois états : chargement, erreur, et succès. ```swift // AsyncDataView.swift struct AsyncDataView: View { @State private var questions: [Question] = [] @State private var isLoading = true @State private var errorMessage: String? var body: some View { Group { if isLoading { // État : chargement en cours ProgressView("Chargement...") } else if let error = errorMessage { // État : erreur VStack(spacing: 16) { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundColor(.orange) Text(error) Button("Réessayer") { Task { await loadQuestions() } } } } else { // État : succès, afficher les données List(questions) { question in Text(question.title) } } } // .task se lance automatiquement quand la vue apparaît .task { await loadQuestions() } // Pull-to-refresh .refreshable { await loadQuestions() } } private func loadQuestions() async { isLoading = true errorMessage = nil do { // Appel API asynchrone questions = try await QuestionService.shared.fetchQuestions() } catch { errorMessage = "Impossible de charger les questions" } isLoading = false } } ``` Le modificateur `.task` est essentiel : il lance une tâche asynchrone quand la vue apparaît et l'annule automatiquement quand elle disparaît. Pas de fuite mémoire possible. ## Conclusion SwiftUI est devenu incontournable pour le développement iOS moderne. Avec iOS 18, le framework atteint une maturité qui le rend parfaitement adapté aux applications de production. ### Ce qu'il faut retenir - **Paradigme déclaratif** : décrivez ce que vous voulez, pas comment le construire - **@State et @Binding** : gérez l'état de façon réactive et propagez-le entre vues - **Stacks** : combinez VStack, HStack et ZStack pour des layouts flexibles - **List** : affichez des collections avec peu de code et des interactions natives - **Animations** : utilisez `withAnimation` pour des transitions fluides automatiques - **Async/await** : chargez vos données avec `.task` et gérez les états loading/error ### Checklist - ✅ Comprendre la différence entre impératif (UIKit) et déclaratif (SwiftUI) - ✅ Maîtriser les modificateurs et leur ordre d'application - ✅ Savoir quand utiliser @State vs @Binding vs @Observable - ✅ Construire des layouts avec les Stacks - ✅ Implémenter des listes avec actions (delete, move) - ✅ Animer les changements d'état avec withAnimation SwiftUI vous ouvre les portes du développement d'applications élégantes sur tout l'écosystème Apple. Le meilleur moyen d'apprendre reste de pratiquer : créez un petit projet personnel et expérimentez avec chaque concept de cet article ! --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/ios/swiftui-construire-interfaces-modernes