SwiftUI Cheatsheet
En juin 2025, j’ai commencé à travailler sur 100 Days of SwiftUI. C’est un excellent cours, et je suis vraiment impressionné par la quantité de contenu de qualité et de cours que Paul Hudson propose - et maintient !! Paul, merci beaucoup pour cela ! 🙏🏼
Mais c’est beaucoup de contenu, alors voici mes notes - j’espère sous une forme d’aide-mémoire facile à naviguer. J’ai une structure générale en tête, mais je ne remplirai le contenu que lorsque j’en aurai besoin. Ne vous attendez donc pas à un aperçu complet !
Swift
Pour un aperçu complet, voir Apprenez l’essentiel de Swift en une heure.
Dans le chapitre suivant, j’ai juste ajouté les parties que j’avais besoin de vérifier au moins une fois.
struct & propriétés calculées
struct Employee {
let name: String
var vacationAllocated = 14
var vacationTaken = 0
var vacationRemaining: Int {
vacationAllocated - vacationTaken
}
}
Optionnels
- Les optionnels nous permettent de représenter l’absence de données, ce qui signifie que nous pouvons dire “cet entier n’a pas de valeur” – c’est différent d’un nombre fixe tel que 0.
- Exemple :
var str:String?peut contenir une chaîne ou nil - En conséquence, tout ce qui n’est pas optionnel a définitivement une valeur à l’intérieur, même si ce n’est qu’une chaîne vide.
- Déballer un optionnel est le processus de regarder à l’intérieur d’une boîte pour voir ce qu’elle contient : s’il y a une valeur à l’intérieur, elle est renvoyée pour être utilisée, sinon il y aura nil à l’intérieur.
- Nous pouvons utiliser
if letpour exécuter du code si l’optionnel a une valeur, ouguard letpour exécuter du code si l’optionnel n’a pas de valeur – mais avecguard, nous devons toujours quitter la fonction par la suite.
func printSquare(of number: Int?) {
guard let number = number else {
print("Entrée manquante")
return
}
print("\(number) x \(number) est \(number * number)")
}
- L’opérateur de coalescence nil, ??, déballe et renvoie la valeur d’un optionnel, ou utilise une valeur par défaut à la place.
let new = captains["Serenity"] ?? "N/A"
- Le chaînage optionnel nous permet de lire un optionnel à l’intérieur d’un autre optionnel avec une syntaxe pratique.
- Si une fonction peut générer des erreurs, vous pouvez la convertir en un optionnel en utilisant
try?– vous obtiendrez soit la valeur de retour de la fonction, soit nil si une erreur est générée.
Protocoles et Extensions
protocol Vehicle {
func estimateTime(for distance: Int) -> Int
func travel(distance: Int)
}
extension String {
func trimmed() -> String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
Tableaux & tri
Tous les tableaux ont des méthodes intégrées sort() et sorted() qui peuvent être utilisées pour trier le tableau.
sort()trie le tableau sur placesorted()renvoie un nouveau tableau trié.
Si le tableau est simple, vous pouvez simplement appeler sort() directement, comme ceci, pour trier un tableau sur place :
var names = ["Jemima", "Peter", "David", "Kelly", "Isabella"]
names.sort()
Si vous avez des structures plus complexes, vous devez transmettre la comparaison :
struct User {
var firstName: String
}
var users = [
User(firstName: "Jemima"),
User(firstName: "Peter"),
User(firstName: "David"),
User(firstName: "Kelly"),
User(firstName: "Isabella")
]
users.sort {
$0.firstName < $1.firstName
}
Nous pouvons faire en sorte que nos propres types se conforment à Comparable, et lorsque nous le faisons, nous obtenons également une méthode sorted() sans paramètres. Cela prend deux étapes :
- Ajouter la conformité
Comparableà la définition de User. - Ajouter une méthode appelée
<qui prend deux utilisateurs et retourne true si le premier doit être trié avant le second.
Voici à quoi cela ressemble en code :
struct User: Identifiable, Comparable {
let id = UUID()
var firstName: String
var lastName: String
static func <(lhs: User, rhs: User) -> Bool {
lhs.lastName < rhs.lastName
}
}
Chaînes
- Elles sont spéciales, et il y a beaucoup à savoir…
- Cela ne fonctionne pas :
let name = "Paul"
let firstLetter = name[0]
Dates
Date, DateComponents, et DateFormatter
enum
Les enum sont créés comme ceci :
enum Weekday {
case monday, tuesday, wednesday, thursday, friday
}
Une instruction switch avec enum ressemble à ceci :
switch loadingState {
case .loading:
LoadingView()
case .success:
SuccessView()
case .failed:
FailedView()
}
SwiftUI
- Interactful est un outil sympa pour naviguer et jouer avec les différentes vues et composants.
- Human Interfaces Guideline
Vues
- Tout est une vue dans SwiftUI 😜
- Exécuter du code lorsqu’une vue est affichée, en utilisant
onAppear().
Même ForEach est une vue, c’est pourquoi nous pouvons écrire
ForEach(0..<5) {
Text("Row \($0)")
}
Remarque : Nous ne pouvons pas écrire ForEach(0..<5), car ForEach attend un Range<Int>, pas un ClosedRange<Int> !
Vue ForEach
ForEach est une vue, composée des sous-vues créées à chaque instance de boucle.
Nous l’utilisons généralement pour créer des sous-vues basées sur un compteur ou un tableau.
ForEach avec un tableau :
import SwiftUI
struct ContentView: View {
let items = ["Apple", "Banana", "Cherry"]
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text(item)
}
}
}
}
Saisie de données
TextField
Picker
Picker est utilisé pour choisir une option parmi plusieurs sélections possibles.
Un sélecteur régulier ressemble à ceci :
Picker("Nombre de personnes", selection: $numberOfPeople) {
ForEach(2 ..< 100 , id: \.self) {
Text("\($0) personnes")
}
}

Un Picker peut être modifié avec le modificateur PickerStyle :
Picker("Pourcentage de pourboire", selection: $tipPercentage) {
ForEach(tipPercentages, id: \.self) {
Text($0, format: .percent)
}
}
.pickerStyle(.segmented)

Stepper
Un stepper est un contrôle à deux segments que les gens utilisent pour augmenter ou diminuer une valeur incrémentale.
@State private var count: Int = 0
var body: some View {
Stepper("\(count)",
value: $count,
in: 0...100
)
}
DatePickerpour les dates. Utilisation du paramètredisplayedComponentspour contrôler les dates ou les heures.FormPicker- Barre de navigation
Alertes & Dialogues de Confirmation
Alertes
Les alertes sont une extension d’une vue avec une variable Bool qui décide si elles sont affichées ou non.
struct ContentView: View {
@State private var showingAlert = false
var body: some View {
Button("Afficher l'alerte") {
showingAlert = true
}
.alert("Message important", isPresented: $showingAlert) {
Button("OK") { }
}
}
}
Dialogues de Confirmation
Utilisez-les lorsque de nombreux boutons/options sont disponibles. Pour un exemple de code, consultez ce dépôt.
Remarque : Avant iOS 26, ils glissaient depuis le bas, dans iOS 26, ils apparaissent à l’intérieur de l’écran.
Dialogue de Confirmation dans iOS 18.5 :

Dialogue de Confirmation dans iOS 26 :

Texte
Text est un champ de texte qui décrit du texte.
Remarque : Les champs de texte avec des styles différents peuvent être additionnés pour former un grand champ de texte avec des parties de styles différents à l’intérieur :
Text(page.title)
.font(.headline)
+ Text(": ") +
Text("Description de la page ici")
.italic()
Et vous obtenez un texte avec différents styles combinés :

Listes
Construire des tableaux de données défilants en utilisant List, en particulier comment il peut créer des lignes directement à partir de tableaux de données.
List {
Section {
Label("Soleil", systemImage: "sun.max")
Label("Nuage", systemImage: "cloud")
Label("Pluie", systemImage: "cloud.rain")
}
}
Boutons dans les listes : Lorsque vous placez un bouton dans une liste, l’ENTIER élément de la liste devient cliquable ! S’il y a plus d’un bouton dans une liste, où que vous cliquiez sur l’élément de la liste, cela clique sur TOUS les boutons les uns après les autres !
Pour résoudre ce problème et obtenir le comportement souhaité, utilisez .buttonStyle(.plain)
Il en va de même pour HStack :
HStack {
if label.isEmpty == false {
Text(label)
}
ForEach(1..<maximumRating + 1, id: \.self) { number in
Button {
rating = number
} label: {
image(for: number)
.foregroundStyle(number > rating ? offColor : onColor)
}
}
}
.buttonStyle(.plain)
Images
struct ContentView: View
{
var body: some View {
Image (example)
.resizable ()
.scaledToFit ()
.frame(width: 300, height: 300)}
}

Remplacez ScaledToFit par ScaledToFill et obtenez

struct ContentView: View {
var body: some View {
Image (•example)
.resizable ()
.scaledToFit()
.containerRelativeFrame(horizontal) { size, axis in
size * 0.8
}
}
}

Barre d’outils
Bundle
Lire des fichiers depuis notre bundle d’application en recherchant leur chemin à l’aide de la classe Bundle, y compris le chargement de chaînes depuis celui-ci.
Animations
Couvert dans Jour 32-34. TODO Je dois revoir les clips pour extraire mes notes/aide-mémoire.
- Créer des animations implicitement en utilisant le modificateur
animation(). - Personnaliser les animations avec des délais et des répétitions, et choisir entre des animations ease-in-ease-out et des animations à ressort.
- Attacher le modificateur
animation()aux liaisons, afin que nous puissions animer les changements directement depuis les contrôles de l’interface utilisateur. - Utiliser
withAnimation()pour créer des animations explicites. - Attacher plusieurs modificateurs
animation()à une seule vue afin que nous puissions contrôler la pile d’animations.
Chargement de données
Si c’est synchrone :
View...
.onAppear(loadIt)
?? Comment cela se fait-il lorsque loadIt est asynchrone ??
Passer et retourner des valeurs vers/depuis des vues
Comme vu dans Sélectionner et éditer des annotations de carte
Imaginez que j’ai une vue qui est ouverte comme une feuille et reçoit un Location (étant une struct définie par l’utilisateur) :
struct EditView: View {
@Environment(\.dismiss) var dismiss
var location: Location
@State private var name: String
@State private var description: String
var body: some View {
NavigationStack {
Form {
Section {
TextField("Nom du lieu", text: $name)
TextField("Description", text: $description)
}
}
.navigationTitle("Détails du lieu")
.toolbar {
Button("Enregistrer") {
dismiss()
}
}
}
}
}
Pour passer le Location, je fais un initialiseur supplémentaire :
init(location: Location) {
self.location = location
_name = State(initialValue: location.name)
_description = State(initialValue: location.description)
}
Pour retourner des données au code appelant, je passe une méthode onSave. D’abord, créez une variable supplémentaire dans ma structure de vue :
var onSave: (Location) -> Void
et puis étendez l’initialiseur comme ceci :
init(location: Location, onSave: @escaping (Location) -> Void) {
self.location = location
self.onSave = onSave
_name = State(initialValue: location.name)
_description = State(initialValue: location.description)
}
Cette partie @escaping est importante, et signifie que la fonction est mise de côté pour une utilisation ultérieure, plutôt que d’être appelée immédiatement, et c’est nécessaire ici car la fonction onSave ne sera appelée que lorsque l’utilisateur appuiera sur Enregistrer.
Sheets & NavigationStacks
Pour ouvrir une vue comme une feuille :
struct ContentView: View {
@State private var showingAddExpense = false
var body: some View {
NavigationStack {
VStack {
// Du code ici
}
.navigationTitle("iExpense")
.toolbar {
Button("Ajouter une dépense", systemImage: "plus") {
showingAddExpense = true
}
}
.sheet(isPresented: $showingAddExpense) {
AddView(expenses: expenses)
}
}
}
}
Réseau
Voici comment vous envoyez quelque chose à un point de terminaison HTTPS :
func placeOrder() async {
guard let encoded = try? JSONEncoder().encode(order) else {
print("Échec de l'encodage de la commande")
return
}
let url = URL(string: "https://reqres.in/api/cupcakes")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
do {
let (data, other) = try await URLSession.shared.upload(for: request, from: encoded)
let decodedOrder = try JSONDecoder().decode(Order.self, from: data)
confirmationMessage = "Votre commande de \(decodedOrder.quantity)x cupcakes \(Order.types[decodedOrder.type].lowercased()) est en route !"
showingConfirmation = true
} catch {
print("Échec de la commande : \(error.localizedDescription)")
}
}
Sauvegarde de données
Je connais 3 façons de sauvegarder des données dans Swift/UI :
- UserDefaults : Mieux utilisé pour sauvegarder de petites quantités de données. Par exemple, les paramètres de l’application.
- Écriture dans le répertoire des documents
- SwiftData
Écriture & lecture dans UserDefaults
Nous avons besoin de quelques éléments :
- Nos données doivent être
Codable, afin que nous puissions créer plus tardJSONEncoder - UserDefaults pour sauvegarder et charger nos données
- Un initialiseur personnalisé pour la classe de données, afin qu’elle se charge automatiquement
- Un
didSetpour les données, afin que chaque fois que les données sont ajoutées ou modifiées, elles soient automatiquement sauvegardées.
Rendre les données Codable n’est souvent pas trop difficile : Tant que les composants sont Codable, la classe entière l’est aussi.
Voici à quoi peut ressembler la sauvegarde des données dans un didSet :
var items = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
Et voici à quoi pourrait ressembler un initialiseur qui charge les données :
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Items") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
items = decodedItems
return
}
}
items = []
}
Écriture & lecture dans le répertoire des documents
Voici comment nous écrivons :
“`swift let data = Data(“Message de test”.utf8) let url = URL.documentsDirectory.appending(path: “message.txt”)
do { try data.write(to: url, options: [.atomic, .completeFileProtection]) } catch { print(error.localizedDescription) } “