SCours SwiftUI
Fiche 05.02

Fiche 05.02 — Navigation programmatique avec routes enum

Objectif

Comprendre comment déclencher une navigation depuis une action : bouton, résultat d’API, connexion réussie ou sélection métier.

1. Pourquoi la navigation programmatique ?

NavigationLink est très pratique quand l’utilisateur clique directement sur une cellule.

Mais parfois, la navigation dépend d’une logique :

  • après un login réussi ;
  • après la création d’un compte ;
  • après un scan terminé ;
  • après le choix d’un élément dans une action custom ;
  • après un appel réseau.

Dans ce cas, on peut piloter la navigation avec un état.

2. Créer une enum Route

Une route représente un écran possible.

Swift
import SwiftUI struct User: Hashable, Identifiable { let id: UUID let name: String } enum AppRoute: Hashable { case profile(User) case settings case detail(id: UUID) }

Hashable est nécessaire pour être utilisé avec la navigation typée.

3. Navigation avec path

Swift
struct ProgrammaticNavigationView: View { @State private var path: [AppRoute] = [] var body: some View { NavigationStack(path: $path) { VStack(spacing: 16) { Button("Voir le profil") { let user = User(id: UUID(), name: "Guillaume") path.append(.profile(user)) } Button("Réglages") { path.append(.settings) } } .navigationTitle("Accueil") .navigationDestination(for: AppRoute.self) { route in switch route { case .profile(let user): ProfileView(user: user) case .settings: SettingsView() case .detail(let id): DetailView(id: id) } } } } } struct ProfileView: View { let user: User var body: some View { Text("Profil de \(user.name)") .navigationTitle("Profil") } } struct SettingsView: View { var body: some View { Text("Réglages") .navigationTitle("Réglages") } } struct DetailView: View { let id: UUID var body: some View { Text("Détail : \(id.uuidString)") .navigationTitle("Détail") } }

Ici, on ne met pas directement l’écran dans le bouton. On ajoute une route dans path, et SwiftUI affiche l’écran correspondant.

4. Navigation après action asynchrone

Exemple simplifié après connexion :

Swift
struct LoginNavigationView: View { @State private var path: [AppRoute] = [] @State private var isLoading = false var body: some View { NavigationStack(path: $path) { VStack { Button(isLoading ? "Connexion..." : "Se connecter") { Task { await login() } } .disabled(isLoading) } .navigationDestination(for: AppRoute.self) { route in switch route { case .settings: SettingsView() case .profile(let user): ProfileView(user: user) case .detail(let id): DetailView(id: id) } } } } private func login() async { isLoading = true defer { isLoading = false } // Simulation d’appel réseau try? await Task.sleep(nanoseconds: 500_000_000) let user = User(id: UUID(), name: "Guillaume") path.append(.profile(user)) } }

Dans une vraie app, le ViewModel peut exposer un état, et la vue réagit à cet état pour naviguer.

5. Navigation et ViewModel

Le ViewModel ne devrait pas forcément connaître NavigationStack. Il peut simplement dire :

Swift
@Published var loginSucceeded = false

Puis la vue déclenche la navigation :

Swift
.onChange(of: viewModel.loginSucceeded) { _, succeeded in if succeeded { path.append(.profile(viewModel.user)) } }

Cela garde le ViewModel testable et évite de mélanger UIKit/SwiftUI navigation avec la logique métier.

Points à connaître

Une enum Route rend la navigation plus propre qu’une accumulation de booléens.

Pour une app simple, inutile de créer un router compliqué. Un path local ou un état de navigation suffit souvent.

Pour une app plus grande, on peut créer un objet Router, mais ce n’est pas obligatoire au début.

Résumé

  • La navigation programmatique sert quand la navigation dépend d’une action.
  • Une enum Route représente les écrans possibles.
  • NavigationStack(path:) permet d’ajouter ou retirer des routes.
  • .navigationDestination(for:) convertit une route en écran.
  • Le ViewModel peut signaler un événement, et la vue gère la navigation.