SCours SwiftUI
Fiche 10.02

Fiche 10.02 — Écrans login/register/forgot password

Objectif

Savoir structurer les écrans classiques d’authentification : connexion, inscription et mot de passe oublié.

Le but est de gérer proprement les champs, la validation, le loading et les erreurs.

1. LoginViewModel

Swift
import SwiftUI @MainActor final class LoginViewModel: ObservableObject { @Published var email = "" @Published var password = "" @Published var isLoading = false @Published var errorMessage: String? var isFormValid: Bool { email.contains("@") && password.count >= 8 } func login() async { guard isFormValid else { return } isLoading = true errorMessage = nil do { try await Task.sleep(nanoseconds: 500_000_000) // simulation API isLoading = false } catch { errorMessage = "Impossible de se connecter." isLoading = false } } }

La vue ne doit pas contenir toute la logique de connexion.

2. LoginView

Swift
struct LoginView: View { @StateObject private var viewModel = LoginViewModel() var body: some View { VStack(spacing: AppSpacing.medium) { Text("Connexion") .font(AppTypography.title) TextField("Email", text: $viewModel.email) .textFieldStyle(.roundedBorder) .keyboardType(.emailAddress) .textInputAutocapitalization(.never) .autocorrectionDisabled() SecureField("Mot de passe", text: $viewModel.password) .textFieldStyle(.roundedBorder) if let error = viewModel.errorMessage { Text(error) .foregroundStyle(AppColors.error) .font(AppTypography.caption) } Button { Task { await viewModel.login() } } label: { if viewModel.isLoading { ProgressView() } else { Text("Se connecter") } } .disabled(!viewModel.isFormValid || viewModel.isLoading) Button("Mot de passe oublié ?") { // ouvrir ForgotPasswordView } } .padding(AppSpacing.large) } }

3. RegisterViewModel

Swift
@MainActor final class RegisterViewModel: ObservableObject { @Published var email = "" @Published var password = "" @Published var confirmPassword = "" @Published var acceptsTerms = false @Published var errorMessage: String? var isFormValid: Bool { email.contains("@") && password.count >= 8 && password == confirmPassword && acceptsTerms } func register() async { guard isFormValid else { errorMessage = "Vérifie les champs du formulaire." return } // appel AuthService.register(...) } }

4. ForgotPasswordView

Swift
struct ForgotPasswordView: View { @State private var email = "" @State private var message: String? var body: some View { VStack(spacing: AppSpacing.medium) { Text("Mot de passe oublié") .font(AppTypography.title) TextField("Email", text: $email) .textFieldStyle(.roundedBorder) .keyboardType(.emailAddress) .textInputAutocapitalization(.never) .autocorrectionDisabled() Button("Envoyer le lien") { message = "Si un compte existe, un email sera envoyé." } .disabled(!email.contains("@")) if let message { Text(message) .font(AppTypography.caption) .foregroundStyle(AppColors.textSecondary) } } .padding(AppSpacing.large) } }

5. Points importants

Un écran d’auth réel doit gérer :

  • validation avant appel API ;
  • loading ;
  • erreur compréhensible ;
  • bouton désactivé si invalide ;
  • clavier propre ;
  • stockage session après réussite ;
  • redirection vers l’app principale.

Résumé

  • Login/register sont des formulaires avec validation.
  • La logique doit aller dans un ViewModel.
  • La vue affiche les états : normal, loading, erreur.
  • Le mot de passe oublié est souvent un écran simple avec email.
  • Après login réussi, on met à jour l’AuthState global.