NavigationStack est un conteneur de navigation moderne dans SwiftUI, introduit dans iOS 16+ et remplaçant NavigationView. Selon la Documentation Apple Developer, 2024, NavigationStack gère une pile d'écrans via un chemin de navigation typé (NavigationPath), prend en charge la navigation profonde, le retour programmatique à l'écran racine et la préservation de l'état lors des modifications de données. Contrairement à NavigationView, NavigationStack ne nécessite pas d'encapsulation dans un conteneur supplémentaire et fournit une liaison directe au chemin de navigation.
Points clés
NavigationStack est une vue conteneur qui implémente la navigation basée sur une pile (LIFO). Elle gère l'historique des transitions, vous permettant de pousser de nouveaux écrans sur la pile et de revenir en arrière via le bouton « Précédent » du système ou par programmation. NavigationStack fait partie de SwiftUI depuis iOS 16, iPadOS 16, macOS 13, watchOS 9 et tvOS 16.
La principale innovation de NavigationStack est le chemin de navigation typé. Au lieu de spécifier directement une destination lors de la création d'un NavigationLink, vous placez une valeur dans le chemin, et la vue de destination est enregistrée séparément via le modificateur .navigationDestination(for:destination:). Cela sépare la navigation du rendu, rendant le code plus modulaire et testable.
Selon la WWDC 2022 (Session 10054), NavigationStack utilise un nouveau mécanisme de navigation basé sur ObservableObject et le cycle de vie de SwiftUI. Contrairement à NavigationView qui reposait sur UINavigationController en interne, NavigationStack est entièrement implémenté en SwiftUI, améliorant la prévisibilité et la compatibilité avec le cycle de vie de SwiftUI.
NavigationStack accepte une vue racine et un chemin de navigation optionnel (Binding à NavigationPath ou un tableau de valeurs Hashable). Tous les écrans enfants sont poussés sur la pile via NavigationLink ou en ajoutant programmatiquement des valeurs au chemin.
NavigationView était le principal conteneur de navigation dans SwiftUI avant iOS 16. Il gérait automatiquement UINavigationController en interne, ce qui entraînait plusieurs problèmes : comportement imprévisible lors des modifications de données, complexité de la navigation programmatique et absence de typage fort.
| Caractéristique | NavigationStack (iOS 16+) | NavigationView (iOS 13–15) |
|---|---|---|
| Type de navigation | Pile (LIFO) | Pile (LIFO) |
| Chemin de navigation | Typé (NavigationPath) | Non supporté |
| Deep linking | Support intégré | Nécessite des solutions |
| Retour programmatique | Via le chemin (pop, popToRoot) | dismiss, presentationMode |
| Sous le capot | SwiftUI natif | UINavigationController |
| Compatibilité | iOS 16+ | iOS 13+ |
Avantage clé de NavigationStack — la navigation typée. Vous définissez le chemin comme un tableau de types spécifiques (ou NavigationPath pour les piles hétérogènes) et enregistrez une destination pour chaque type. Cela élimine les erreurs d'incompatibilité de types et rend la navigation prévisible.
NavigationView est déprécié dans iOS 17. Apple recommande de migrer vers NavigationStack pour tous les nouveaux projets et lors de la mise à jour de la version minimale vers iOS 16.
NavigationPath est un type représentant le chemin de navigation dans NavigationStack. Il peut stocker des valeurs hétérogènes (AnyHashable) ou être utilisé avec un type spécifique via Binding à un tableau [T: Hashable]. NavigationPath s'encode et se décode automatiquement pour la préservation de l'état.
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: String.self) { value in
DetailView(id: value)
}
.navigationDestination(for: Int.self) { value in
NumberView(number: value)
}
}
}
func goToRoot() {
path.removeLast(path.count)
}
func pushDeepLink() {
path.append("detail_42")
}
}
Pile hétérogène : NavigationPath peut contenir des valeurs de différents types s'ils implémentent Hashable. Par exemple, le premier écran peut accepter un String (ID), le second un Int (nombre), le troisième un enum Route personnalisé. Chaque type enregistre un .navigationDestination séparé pour l'affichage.
Support Codable : NavigationPath implémente Codable si toutes les valeurs du chemin sont également Codable + Hashable. Cela permet de sauvegarder et de restaurer l'état de navigation lors du redémarrage de l'application ou du passage en arrière-plan.
.navigationDestination(for:destination:) — un modificateur qui enregistre une vue de destination pour un type de données spécifique. Lorsque NavigationLink place une valeur de ce type dans le chemin, SwiftUI trouve automatiquement le .navigationDestination correspondant et crée l'écran.
enum AppRoute: Hashable {
case profile(UserID)
case settings
case about
}
struct AppNavigation: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .profile(let id):
ProfileView(userId: id)
case .settings:
SettingsView()
case .about:
AboutView()
}
}
}
}
}
// Navigate: path.append(AppRoute.profile("user_123"))
Règle importante : .navigationDestination doit être appliqué à une vue qui se trouve à l'intérieur de NavigationStack, et avant que NavigationLink ne place une valeur dans le chemin. Il est généralement ajouté à la vue racine ou à un conteneur de section. Si aucun .navigationDestination n'est trouvé pour le type de la valeur, la transition n'aura pas lieu.
Selon SwiftUI Engineering (2023), .navigationDestination peut être enregistré à différents niveaux de la hiérarchie. SwiftUI recherche la destination correspondante la plus proche lors de la navigation. Cela permet de remplacer la destination pour un type dans différentes parties de l'application.
Patron 1 : navigation via enum Route. Définissez un enum avec des valeurs associées pour tous les écrans de l'application. Utilisez un .navigationDestination pour AppRoute et un switch pour le routage. Cela fournit une source unique de vérité pour toutes les transitions possibles dans l'application.
struct StoreView: View {
@State private var path: [ProductRoute] = []
var body: some View {
NavigationStack(path: $path) {
ProductGrid()
.navigationDestination(for: ProductRoute.self) { route in
switch route {
case .detail(let product):
ProductDetail(product: product)
case .reviews(let productId):
ReviewsView(productId: productId)
}
}
}
}
}
enum ProductRoute: Hashable {
case detail(Product)
case reviews(String)
}
Patron 2 : navigation programmatique et deep linking. NavigationStack permet la gestion programmatique de la pile : ajout, suppression d'écrans et retour à la racine. Cela est nécessaire pour les notifications push, les deep links et la restauration de la navigation après un redémarrage.
Patron 3 : tableau de types au lieu de NavigationPath. Si tous les écrans utilisent le même type (par exemple, String ou un enum personnalisé), utilisez Binding à [T]. Cela offre un typage plus fort et de meilleures performances que NavigationPath. NavigationPath est justifié pour les piles hétérogènes avec différents types d'écrans.
Selon Point-Free (2024), NavigationStack avec enum Route est la manière préférée d'organiser la navigation dans les applications SwiftUI. Il rend toutes les transitions possibles explicites, typées et testables, ce qui est particulièrement important pour les grands projets avec des dizaines d'écrans.
Foire aux questions
NavigationStack — un conteneur de navigation SwiftUI (iOS 16+) qui gère une pile d'écrans via un chemin typé. Il a remplacé NavigationView, offrant un support pour le deep linking, la navigation programmatique et la préservation de l'état.
NavigationStack utilise un chemin typé (NavigationPath) au lieu de lier directement NavigationLink à une destination. Il prend en charge la navigation programmatique, le deep linking et Codable pour la préservation de l'état. Il fonctionne sur SwiftUI, pas via UINavigationController.
NavigationPath est un type représentant une séquence d'écrans dans la pile. Vous ajoutez des valeurs au chemin via path.append() ou NavigationLink avec value. Chaque type enregistre un .navigationDestination pour l'affichage. NavigationPath prend en charge Codable et la préservation automatique de l'état.
Grâce à la gestion programmatique du chemin : après avoir traité l'URL, appelez path.append() avec la valeur de route correspondante. NavigationStack affiche automatiquement l'écran cible. Retour à la racine — path.removeLast(path.count).
Oui, si votre version minimale est iOS 16+. NavigationView est déprécié dans iOS 17. La migration offre une navigation typée, un support du deep linking et un meilleur alignement avec le cycle de vie de SwiftUI. Pour les projets ciblant iOS 15 et inférieur, continuez à utiliser NavigationView pour l'instant.
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