SwiftUI est un framework déclaratif d'Apple pour construire des interfaces utilisateur sur toutes les plateformes de l'écosystème. Au lieu de décrire les étapes de manière impérative, le développeur déclare à quoi l'interface doit ressembler et SwiftUI gère son rendu et sa mise à jour. Selon la Apple Developer Documentation (2025), SwiftUI prend en charge iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+ et tvOS 15+ et utilise le View Protocol comme bloc de construction de base pour tous les composants d'interface.
Points clés
body est la base de tout composant d'interface SwiftUI, renvoyant une description d'écran par composition de vues.SwiftUI est un framework déclaratif présenté par Apple en 2019 pour remplacer UIKit dans les nouveaux projets. Au lieu de créer manuellement des instances UIView et de les ajouter à la hiérarchie, le développeur décrit l'interface à travers des structures conformes au protocole View. SwiftUI calcule automatiquement la différence entre l'état actuel et le nouvel état et ne réaffiche que les parties modifiées en utilisant son propre moteur de rendu.
Le framework est écrit en Swift en utilisant des value semantics (structures, pas de classes), ce qui rend les composants d'interface légers et thread-safe. Contrairement à UIKit, où UIViewController peut peser 200+ octets à cause du runtime Objective-C, une SwiftUI View est simplement une structure de quelques octets. Ceci est particulièrement important pour watchOS avec sa mémoire limitée.
La même description de View fonctionne sur iPhone, iPad, Mac, Apple Watch, Apple TV et Apple Vision Pro. SwiftUI adapte l'interface à la plateforme : gestes tactiles sur iOS, combinaisons de clavier sur macOS, défilement Digital Crown sur watchOS. Cela réduit le temps de développement pour les entreprises qui publient des applications sur plusieurs plateformes Apple, mais nécessite une configuration supplémentaire pour les éléments spécifiques à chaque plateforme.
Dans SwiftUI, chaque écran est une structure qui implémente le protocole View avec une seule exigence : une propriété calculée body de type some View. Le mot-clé some (type opaque) cache le type concret de la vue, permettant à SwiftUI d'optimiser le rendu. À l'intérieur de body, le développeur combine des composants prêts à l'emploi — Text, Image, Button, List — en utilisant ViewBuilder, qui assemble plusieurs vues en une seule.
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("Bonjour, \(name) !")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
Dans l'exemple, VStack (pile verticale) contient Text et Image. La valeur de name est passée via l'initialiseur de la structure — c'est ainsi que fonctionne DI (Dependency Injection) dans SwiftUI sans conteneurs DI externes. Chaque modificateur renvoie une nouvelle vue avec la modification appliquée, sans muter l'originale. Ceci est possible grâce à l'immuabilité des value types.
ViewBuilder est un result builder annoté avec @resultBuilder qui assemble jusqu'à 10 vues en une seule. À l'intérieur de body, on peut utiliser if/else, switch et ForEach sans wrappers supplémentaires. ForEach fonctionne avec des éléments Identifiable — chaque vue reçoit un identifiant unique pour une animation correcte lors de l'insertion/suppression.
Dans SwiftUI, l'état détermine quel contenu est affiché à l'écran. Lorsque l'état change, SwiftUI recrée le body de la vue dépendante et compare le résultat avec le précédent en utilisant un algorithme de diff. Pour stocker l'état, on utilise des property wrappers — chacun résout sa tâche spécifique : état local, connexion avec une vue fille ou modèle de données externe.
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("Compteur : \(count)")
Button("Incrémenter") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State stocke une valeur locale simple (Int, String, Bool) dans la structure View. SwiftUI déplace la mémoire de la structure vers un stockage séparé — donc une propriété avec @State peut être mutée même si View est un value type. @ObservableObject est pour les classes avec des propriétés @Published, dont les changements notifient automatiquement SwiftUI de la nécessité de se réafficher.
@Binding crée une liaison bidirectionnelle avec une source de données située dans la vue parent. Le parent passe $variable (projected value), et l'enfant lit et écrit la valeur via le binding. Cela permet de déplacer la saisie de texte ou un interrupteur dans un composant séparé tout en conservant l'état dans le parent. Sans @Binding, chaque changement nécessiterait une closure de callback pour transmettre la nouvelle valeur vers le haut.
Avant iOS 16, la navigation dans SwiftUI était construite sur NavigationView — une API legacy avec un comportement complexe sur iPad (split view, double colonne). À partir d'iOS 16, Apple recommande NavigationStack — une alternative simplifiée avec des routes type-safe. Le développeur définit un enum des routes possibles, et NavigationStack gère automatiquement la pile d'écrans avec prise en charge des deep links et du retour à la racine.
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("Écran de détail",
value: Route.detail(id: 42))
NavigationLink("Paramètres",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
Les routes conformes à Hashable permettent d'utiliser n'importe quel type de données pour passer des paramètres. navigationDestination(for:destination:) associe le type de route à la vue cible. L'avantage par rapport à la navigation UIKit est qu'aucun réaffichage n'est nécessaire lors de l'ajout d'une nouvelle route : il suffit d'ajouter un case à l'enum et un handler dans le switch. Les deep links sont traités via processDeepLink sur NavigationStack.
Pour la navigation programmatique (après connexion, minuteur ou réponse du serveur), on utilise @State avec l'initialiseur NavigationLink : NavigationLink(isActive: $isActive). Lorsque isActive = true, la transition s'effectue sans toucher l'utilisateur. Une alternative est le binding du tableau $path dans NavigationStack : $path.append(Route.detail(id: 1)).
Modifier est une méthode qui renvoie une copie modifiée de la vue. Contrairement à UIKit, où la configuration des propriétés se fait par mutation d'une vue existante, SwiftUI crée une nouvelle valeur avec la modification appliquée. L'enchaînement de modificateurs construit l'interface finale à partir de transformations séquentielles : police → padding → couleur → ombre → geste.
Apple fournit plus de 200 modificateurs intégrés. Les plus courants : .font(), .foregroundColor(), .padding(), .background(), .cornerRadius(), .shadow(), .opacity(), .offset(). L'ordre des modificateurs est important : .padding() avant .background() remplit la zone avec le padding, après — seulement la zone intérieure. Les modificateurs personnalisés sont créés via le protocole ViewModifier.
Les modificateurs peuvent être appliqués conditionnellement via l'opérateur ternaire : .foregroundColor(isError ? .red : .primary). Pour l'animation, on utilise .animation(.easeInOut, value: state) — le modificateur d'animation est lié à une propriété d'état spécifique. Lorsque cette propriété change, SwiftUI anime la transition entre l'ancienne et la nouvelle valeur. L'animation fonctionne avec opacity, offset, scale, rotation, la taille et la couleur — chaque propriété a un AnimatableParameter correspondant.
Pour les animations personnalisées, .transition (apparition/disparition) et .matchedGeometryEffect (transition fluide d'un élément entre deux conteneurs) sont disponibles. Ce dernier est utilisé pour les hero animations dans les listes : une icône dans une cellule de liste se transforme en douceur en une grande image sur l'écran de détail.
Le choix entre SwiftUI et UIKit est l'un des premiers dilemmes du développeur iOS. Les deux frameworks sont pris en charge par Apple mais résolvent le problème de construction d'interface de manière fondamentalement différente : SwiftUI déclarativement, UIKit impérativement. La différence se manifeste dans la gestion d'état, la navigation, les performances et la compatibilité.
| Aspect | SwiftUI | UIKit |
|---|---|---|
| Approche | Déclarative : quoi afficher | Impérative : comment construire |
| État | Property Wrappers, réaffichage automatique | Manuel : reloadData, setNeedsLayout |
| Code UI | Compact, chaînes de modificateurs | Verbeux, NSCoder/Storyboard/contraintes |
| Performances | Élevées sur iOS 17+, algorithme de diff | Pic sur iOS 12–16, contrôle direct |
| Version minimale | iOS 15+ (support complet) | iOS 2+ (toutes versions) |
Pour les nouveaux projets avec une version minimale iOS 17, Apple recommande SwiftUI comme framework principal. UIKit reste nécessaire pour les interfaces nécessitant un contrôle fin sur le rendu (UICollectionViewLayout personnalisé, scènes CAAnimation complexes) ou la prise en charge d'iOS 12–14. De nombreux projets utilisent une approche hybride : SwiftUI via UIHostingController est intégré dans une application UIKit, et UIViewRepresentable permet d'utiliser des composants UIKit dans la hiérarchie SwiftUI.
Questions fréquentes
Oui, via UIHostingController (SwiftUI dans UIKit) et UIViewRepresentable (UIKit dans SwiftUI). C'est une approche hybride, populaire lors de la migration.
iOS 17 offre des fonctionnalités complètes : NavigationStack, Observation framework, Swift Charts. iOS 15 est le seuil minimum pour la production.
La cause la plus fréquente est la modification d'une propriété @Published sur un thread d'arrière-plan. ObservableObject doit envoyer les modifications sur le main actor : @MainActor class ViewModel.
Utilisez .debounce via Combine : Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).
Oui, via les modificateurs Gesture : DragGesture, LongPressGesture, MagnificationGesture, RotationGesture. Combinez-les avec .simultaneousGesture() et .sequenced().
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi