SCours SwiftUI
Fiche 03.04

Fiche 03.04 — Cards et containers réutilisables en SwiftUI

Objectif

Comprendre comment créer des blocs visuels réutilisables comme des cards, containers, sections et encadrés de contenu.


1. L’idée à comprendre

Dans une app SwiftUI, beaucoup d’écrans sont composés de blocs :

  • card de profil ;
  • card d’article ;
  • card de statistique ;
  • section de réglages ;
  • bloc d’information ;
  • encadré d’erreur ;
  • container avec titre.

Au lieu de répéter partout :

Swift
.padding() .background(.gray.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 16))

Tu peux créer des composants ou des modifiers réutilisables.


2. Card simple

Swift
struct SimpleCardView: View { let title: String let subtitle: String var body: some View { VStack(alignment: .leading, spacing: 8) { Text(title) .font(.headline) Text(subtitle) .font(.subheadline) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(.gray.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 16)) } }

Utilisation :

Swift
SimpleCardView( title: "SwiftUI", subtitle: "Créer des interfaces déclaratives." ) .padding()

3. Card avec contenu personnalisable

Pour rendre une card très réutilisable, tu peux utiliser @ViewBuilder.

Swift
struct AppCard<Content: View>: View { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { content .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(.gray.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 16)) } }

Utilisation :

Swift
AppCard { VStack(alignment: .leading, spacing: 8) { Text("Titre") .font(.headline) Text("Contenu de la card") .foregroundStyle(.secondary) } } .padding()

L’avantage : AppCard gère le style, mais le contenu reste libre.


4. Card avec titre intégré

Swift
struct TitledCard<Content: View>: View { let title: String let content: Content init( title: String, @ViewBuilder content: () -> Content ) { self.title = title self.content = content() } var body: some View { VStack(alignment: .leading, spacing: 12) { Text(title) .font(.headline) content } .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(.gray.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 16)) } }

Utilisation :

Swift
TitledCard(title: "Statistiques") { HStack { Text("Posts") Spacer() Text("42") .fontWeight(.bold) } } .padding()

5. Card cliquable

Une card peut être cliquable en utilisant Button.

Swift
struct ActionCard: View { let title: String let subtitle: String let action: () -> Void var body: some View { Button { action() } label: { HStack(spacing: 12) { VStack(alignment: .leading, spacing: 6) { Text(title) .font(.headline) .foregroundStyle(.primary) Text(subtitle) .font(.subheadline) .foregroundStyle(.secondary) } Spacer() Image(systemName: "chevron.right") .foregroundStyle(.secondary) } .padding() .background(.gray.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 16)) } } }

Utilisation :

Swift
ActionCard( title: "Profil", subtitle: "Modifier mes informations" ) { print("Ouvrir profil") } .padding()

6. Card avec image

Swift
struct ArticleCardView: View { let title: String let subtitle: String let systemImage: String var body: some View { HStack(spacing: 12) { Image(systemName: systemImage) .font(.title) .frame(width: 48, height: 48) .background(.blue.opacity(0.15)) .foregroundStyle(.blue) .clipShape(RoundedRectangle(cornerRadius: 12)) VStack(alignment: .leading, spacing: 4) { Text(title) .font(.headline) Text(subtitle) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(2) } Spacer() } .padding() .background(.gray.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 16)) } }

Utilisation :

Swift
ArticleCardView( title: "Navigation SwiftUI", subtitle: "Comprendre NavigationStack et NavigationPath.", systemImage: "arrow.triangle.branch" ) .padding()

7. Créer un ViewModifier de card

Si tu veux appliquer le même style à beaucoup de vues, tu peux créer un ViewModifier.

Swift
struct CardModifier: ViewModifier { func body(content: Content) -> some View { content .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(.gray.opacity(0.1)) .clipShape(RoundedRectangle(cornerRadius: 16)) } }

Extension :

Swift
extension View { func cardStyle() -> some View { modifier(CardModifier()) } }

Utilisation :

Swift
VStack(alignment: .leading) { Text("Titre") .font(.headline) Text("Description") .foregroundStyle(.secondary) } .cardStyle() .padding()

8. Section réutilisable

Swift
struct AppSection<Content: View>: View { let title: String let content: Content init( title: String, @ViewBuilder content: () -> Content ) { self.title = title self.content = content() } var body: some View { VStack(alignment: .leading, spacing: 12) { Text(title) .font(.title3) .fontWeight(.semibold) content } .frame(maxWidth: .infinity, alignment: .leading) } }

Utilisation :

Swift
AppSection(title: "Compte") { AppCard { Text("Informations personnelles") } AppCard { Text("Sécurité") } } .padding()

9. Où ranger ces composants ?

Pour un projet structuré :

Texte
Shared/ Components/ Containers/ AppCard.swift TitledCard.swift AppSection.swift Modifiers/ CardModifier.swift

Si une card est spécifique à une feature :

Texte
Features/ Articles/ Views/ ArticleCardView.swift

10. Preview

Swift
#Preview { ScrollView { VStack(spacing: 16) { AppCard { Text("Card simple") } TitledCard(title: "Profil") { Text("Guillaume") Text("Développeur iOS") .foregroundStyle(.secondary) } ActionCard( title: "Réglages", subtitle: "Ouvrir les paramètres" ) {} ArticleCardView( title: "SwiftUI", subtitle: "Apprendre à créer des interfaces propres.", systemImage: "swift" ) } .padding() } }

11. Points à connaître

Préfère un composant si le contenu a une logique spécifique

Exemple :

Swift
ArticleCardView(article: article)

C’est mieux qu’une card générique si l’affichage est vraiment propre aux articles.


Préfère un modifier si tu veux seulement réutiliser un style

Exemple :

Swift
Text("Contenu") .cardStyle()

C’est utile quand le contenu varie beaucoup, mais le style reste le même.


Évite les styles dupliqués partout

Si tu répètes plusieurs fois :

Swift
.padding() .background(...) .clipShape(...)

c’est souvent le signe qu’il faut créer une card ou un modifier.


Résumé

À retenir :

  • une card est un container visuel réutilisable ;
  • @ViewBuilder permet de créer une card avec contenu libre ;
  • une card peut être statique ou cliquable ;
  • un ViewModifier est pratique pour réutiliser seulement un style ;
  • une section réutilisable peut organiser plusieurs cards ;
  • range les containers partagés dans Shared/Components ;
  • crée une card spécifique si le contenu appartient à une feature précise.