Cours de développement mobile
iOS & SwiftUI
Développement d'applications pour iPhone et iPad
Avec le langage Swift et le framework déclaratif SwiftUI
Compréhension de l'écosystème et de la syntaxe
Introduction
Ce cours vous présente le développement d'applications iOS avec le framework SwiftUI. Vous êtes
maintenant familier avec trois frameworks de développement mobile déclaratifs : Jetpack Compose
(Android natif), Flutter (multi-plateforme avec moteur Skia), et React Native (composants natifs avec
JavaScript). SwiftUI complète le tableau pour la plateforme Apple.
La grande nouvelle : SwiftUI suit exactement la même philosophie que ce que vous connaissez. Vous
décrivez votre UI en code, l'état est géré de manière réactive, les composants sont composables. Si vous
savez écrire un composable Compose, vous saurez écrire une View SwiftUI en moins d'une heure.
Prérequis matériel
Pour développer iOS, vous avez OBLIGATOIREMENT besoin d'un Mac (avec macOS récent) car Xcode, l'IDE
Apple, ne fonctionne que sur Mac. Il n'existe AUCUNE solution gratuite pour développer iOS sur Windows ou
Linux. Heureusement, le cours est conçu pour être compris même sans accès régulier à un Mac.
Ce que vous allez apprendre
• L'écosystème Apple : matériel requis, compte développeur, App Store.
• Le langage Swift, dans ses aspects utiles à SwiftUI.
• Xcode et le simulateur iOS : présentation visuelle.
• Les vues fondamentales (Text, Image, Button).
• Les layouts (VStack, HStack, ZStack).
• La gestion d'état (@State, @Binding, @ObservedObject).
• Les listes (List, ForEach).
• Les formulaires et la navigation.
• Les appels API avec URLSession et Codable.
• La publication d'une app sur l'App Store.
Plan du cours en 11 chapitres
Chapitre Équivalent dans ce que vous savez
1 L'écosystème Apple (culture générale)
2 Le langage Swift Kotlin / Dart
3 Xcode et le simulateur Android Studio / VS Code
4 Vues fondamentales Composables / Widgets / Composants
5 Layouts (VStack, HStack, ZStack) Column, Row, Box
6 Gestion d'état avec @State remember / setState / useState
7 Listes (List et ForEach) LazyColumn / [Link] / FlatList
8 Formulaires et navigation Form / NavigationStack
9 Appels API avec URLSession Retrofit / http / fetch
10 Publication sur l'App Store (culture pro)
11 Tableau de correspondance final (synthèse)
1. L'écosystème Apple
Avant de parler de code, il est essentiel de comprendre l'écosystème dans lequel vous allez évoluer si vous
développez pour iOS. Apple impose des contraintes fortes mais offre en contrepartie un environnement
très cohérent et une clientèle au pouvoir d'achat élevé.
1.1. Le matériel
Pour développer iOS, vous avez besoin de :
• Un Mac : MacBook, iMac, Mac mini. Xcode (l'IDE Apple) ne fonctionne PAS sur Windows ou
Linux. Pas de contournement gratuit possible.
• macOS récent : généralement la version actuelle ou N-1 pour développer pour les iPhones
récents.
• Optionnel mais recommandé : un iPhone : le simulateur Xcode est très bon, mais tester sur un
vrai appareil est plus fiable.
• Pour publier : un compte Apple Developer : 99 $ US par an. Sans ce compte, vous pouvez
développer et tester, mais pas publier sur l'App Store.
1.2. Les outils logiciels
• Xcode : l'IDE officiel d'Apple. Gratuit, à télécharger sur le Mac App Store. Inclut éditeur de code,
simulateur iOS, outils de débogage, Interface Builder.
• Swift : le langage moderne d'Apple, open source depuis 2015. C'est ce que vous utiliserez.
• SwiftUI : le framework UI moderne, déclaratif, sorti en 2019. C'est notre sujet.
• UIKit : l'ancien framework impératif (depuis 2008). Encore très utilisé dans les apps existantes,
mais on ne s'en occupe pas ici.
1.3. Les plateformes Apple
Bonus important : SwiftUI permet de développer pour TOUTES les plateformes Apple à partir d'une seule
base de code.
Plateforme Appareils Spécificité
iOS iPhone Téléphone (mobile)
iPadOS iPad Tablette (multitâche, écran large)
macOS Mac Ordinateur (fenêtres, souris)
Plateforme Appareils Spécificité
watchOS Apple Watch Très petit écran, geste digital
tvOS Apple TV Télévision (télécommande)
visionOS Vision Pro Réalité mixte (depuis 2024)
SwiftUI = un seul code pour 6 plateformes
Avec SwiftUI, une même base de code peut s'adapter automatiquement à toutes les plateformes Apple. C'est
plus puissant que Flutter (qui ne fait pas watchOS ou tvOS natif). Pour ce cours, on se concentre sur iOS
(iPhone).
1.4. Apple Developer Program : 99 $ par an
Pour publier une application sur l'App Store, vous devez vous inscrire au Apple Developer Program au tarif
de 99 $ US par an (renouvellement annuel).
Ce que cela couvre :
• Possibilité de publier des apps sur l'App Store.
• Accès aux bêtas d'iOS, macOS, Xcode.
• Outils d'analyse (TestFlight pour les tests bêta, App Store Connect pour les statistiques).
• Certificats de signature des applications.
• Support technique limité.
Sans compte payant, vous pouvez :
• Développer et lancer une app sur votre propre iPhone (avec une signature qui expire après 7
jours).
• Utiliser le simulateur autant que vous voulez.
• Compiler et tester votre code.
Le coût de publication App Store vs Google Play
Google Play : 25 $ une seule fois, à vie. Apple : 99 $ par an. Sur 10 ans : 25 $ Google contre 990 $ Apple. C'est
un facteur à prendre en compte pour les petits développeurs ou les projets étudiants.
1.5. Le processus de validation App Store
Contrairement à Google Play (validation quasi automatique en quelques heures), Apple effectue une
revue humaine de chaque application. Le délai est de 24 à 72 heures en moyenne. Les critères sont stricts
:
• Stabilité (pas de crash).
• Respect des Human Interface Guidelines (cohérence visuelle iOS).
• Pas de contenu interdit (violence, adulte, copies d'apps existantes…).
• Métadonnées correctes (description, captures d'écran, classification).
• Permissions justifiées dans l'app.
Si votre app est rejetée, Apple vous indique précisément la raison. Vous corrigez et resoumettez. La
plupart des apps sont acceptées du premier coup si vous suivez les guidelines.
1.6. Pourquoi développer pour iOS au Togo ?
Question légitime quand on sait que iOS représente moins de 10 % du marché africain. Voici pourquoi
cette compétence reste précieuse :
• Marché international : aux États-Unis, en Europe, en Asie, iOS représente 20 à 60 % du marché.
Pour cibler une clientèle internationale, iOS est incontournable.
• Clientèle premium : les utilisateurs iOS dépensent en moyenne 3 fois plus dans les apps que les
utilisateurs Android. Une app payante a plus de chances de réussir sur iOS.
• Profil rare en Afrique francophone : les développeurs iOS sont peu nombreux. Le salaire et la
demande sont en conséquence plus élevés.
• Connaissance complète : un développeur qui maîtrise Compose, Flutter, React Native ET iOS est
un profil très rare et très recherché.
2. Le langage Swift
Swift est le langage créé par Apple en 2014. Il est moderne, expressif, et conçu pour être sûr et
performant. Si vous connaissez Kotlin, vous serez à l'aise immédiatement : les deux langages ont
énormément en commun. Apple et Google ont d'ailleurs collaboré, et Kotlin s'est inspiré de Swift sur
plusieurs points.
2.1. Caractéristiques principales
• Typage statique avec inférence : comme Kotlin, on peut déclarer ou laisser deviner.
• Null-safety : les Optionals (Type?) gèrent les valeurs null, exactement comme en Kotlin.
• Orienté objet ET protocoles : Swift utilise massivement les protocoles (équivalents des
interfaces).
• Performant : compile en code natif, performances comparables à C++.
• Open source : depuis 2015, on peut même l'utiliser sur Linux (côté serveur).
2.2. Variables : let et var
Variables Swift
// Constante (équivalent val Kotlin, final Dart, const JS)
let nom = "Aïcha"
// Variable mutable (équivalent var)
var compteur = 0
compteur = compteur + 1
// Type explicite (optionnel, inféré par défaut)
let age: Int = 25
var prix: Double = 19.99
let connecte: Bool = true
// Pas de point-virgule à la fin (comme Kotlin)
// Mais on peut en mettre si on veut mettre plusieurs instructions sur une ligne :
let a = 1; let b = 2
Swift est très proche de Kotlin pour la déclaration :
Concept Kotlin Swift
Constante val nom = "Aïcha" let nom = "Aïcha"
Variable var x = 0 var x = 0
Type explicite val age: Int = 25 let age: Int = 25
Chaîne "texte" "texte"
Concept Kotlin Swift
Entier Int Int
Décimal Double Double
Booléen Boolean Bool
2.3. Null-safety avec les Optionals
Comme Kotlin, Swift sépare strictement les valeurs qui peuvent être nil (équivalent de null) des valeurs
qui ne peuvent pas l'être.
Optionals Swift
// Variable normale : ne peut PAS être nil
var nom: String = "Aïcha"
// nom = nil // ❌ Erreur de compilation
// Optional : peut être nil
var prenom: String? = "Kofi"
prenom = nil // ✓ OK
// Accès sécurisé avec ? (comme Kotlin)
let longueur = prenom?.count // Int? (peut être nil)
// Opérateur ?? (équivalent ?: en Kotlin)
let nomAffiche = prenom ?? "inconnu"
// Forcer le déballage avec ! (à utiliser avec prudence)
let longueurForcee = prenom!.count // ⚠️ crash si prenom est nil
// if let : déballage sûr
if let valeur = prenom {
print("Prénom : \(valeur)")
} else {
print("Pas de prénom")
}
// guard let : déballage avec sortie anticipée
func saluer(nom: String?) {
guard let n = nom else {
print("Pas de nom")
return
}
print("Bonjour \(n)")
}
Comparaison directe avec Kotlin :
Concept Kotlin Swift
Non-nullable String String
Nullable String? String?
Accès sécurisé obj?.method() obj?.method()
Valeur par défaut obj ?: "defaut" obj ?? "defaut"
Forçage obj!! obj!
Déballage sûr obj?.let { ... } if let v = obj { ... }
Mot pour null null nil
2.4. Fonctions
Fonctions Swift
// Fonction simple
func additionner(a: Int, b: Int) -> Int {
return a + b
}
// Avec valeur par défaut
func saluer(nom: String = "monde") -> String {
return "Bonjour \(nom)"
}
// Sans valeur de retour
func afficher(message: String) {
print(message)
}
// Avec étiquettes externes différentes (caractéristique Swift)
func envoyer(message texte: String, vers destinataire: String) {
print("Envoyer \(texte) à \(destinataire)")
}
// Appel
envoyer(message: "Bonjour", vers: "Aïcha")
// L'utilisateur écrit "message:" et "vers:" mais à l'intérieur on a "texte" et
"destinataire"
// Retour multiple (tuple)
func minMax(numeros: [Int]) -> (min: Int, max: Int) {
return ([Link]()!, [Link]()!)
}
let resultat = minMax(numeros: [3, 8, 1, 5])
print([Link]) // 1
print([Link]) // 8
Étiquettes externes en Swift
Une spécificité Swift : on peut avoir deux noms pour un paramètre — un nom EXTERNE (utilisé à l'appel) et un
nom INTERNE (utilisé dans le corps de la fonction). Cela rend le code très lisible : envoyer(message: "Salut",
vers: "Kofi") se lit comme une phrase. C'est inspiré d'Objective-C.
2.5. Structures et classes
Swift a deux types principaux pour créer des objets :
• struct : type par valeur (copié lors de l'affectation). Recommandé par défaut.
• class : type par référence (partagé). Pour les cas complexes avec héritage.
Struct et class
// Struct (équivalent data class Kotlin pour les besoins simples)
struct Etudiant {
var nom: String
var age: Int
var moyenne: Double
// Méthode
func estAdmis() -> Bool {
return moyenne >= 10.0
}
// Propriété calculée
var statut: String {
return estAdmis() ? "Admis" : "Non admis"
}
}
// Utilisation
let aicha = Etudiant(nom: "Aïcha", age: 20, moyenne: 14.5)
print([Link]) // Admis
print([Link]()) // true
// Class (héritage possible)
class Vehicule {
var marque: String
init(marque: String) {
[Link] = marque
}
func demarrer() {
print("\(marque) démarre")
}
}
class Voiture: Vehicule {
var nombrePortes: Int
init(marque: String, nombrePortes: Int) {
[Link] = nombrePortes
[Link](marque: marque)
}
}
2.6. Tableaux et dictionnaires
Collections
// Tableau (équivalent List en Kotlin)
var notes = [12, 15, 8, 18, 6]
[Link](20)
print([Link]) // 6
print(notes[0]) // 12
// Dictionnaire (équivalent Map)
var ages = ["Aïcha": 20, "Kofi": 22, "Abla": 19]
ages["Yawo"] = 25
print(ages["Aïcha"]) // Optional(20) — c'est un Int?
// Méthodes fonctionnelles (style Kotlin / Dart)
let notesAdmises = [Link] { $0 >= 10 }
let doublees = [Link] { $0 * 2 }
let somme = [Link](0, +)
// $0, $1 sont les paramètres implicites des closures (équivalent it en Kotlin)
// On peut aussi écrire explicitement : { n in n >= 10 }
2.7. Asynchrone : async / await
Depuis Swift 5.5 (2021), Swift propose async / await intégrés au langage, exactement comme Kotlin
coroutines, Dart async, et JavaScript async.
Async/await Swift
// Fonction asynchrone qui peut prendre du temps
func chargerProduits() async throws -> [Produit] {
let url = URL(string: "[Link]
let (data, _) = try await [Link](from: url)
let produits = try JSONDecoder().decode([Produit].self, from: data)
return produits
}
// Utilisation
Task {
do {
let produits = try await chargerProduits()
print("\([Link]) produits chargés")
} catch {
print("Erreur : \(error)")
}
}
Mots-clés à retenir :
• async : marque une fonction comme asynchrone (équivalent suspend en Kotlin).
• await : attend la valeur sans bloquer (identique aux autres langages).
• throws : indique que la fonction peut lancer une erreur.
• try : appelle une fonction qui peut échouer.
• Task { ... } : lance une coroutine (équivalent launch en Kotlin).
2.8. Comparaison rapide Swift / Kotlin / Dart / JavaScript
Concept Kotlin Swift Dart JavaScript
Constante val let final const
Variable var var var let
Null null nil null null
Optional Type? Type? Type? (rien)
Interpolation "$nom" "\(nom)" '$nom' `${nom}`
Async suspend async Future async
Liste listOf() [1, 2, 3] [1, 2, 3] [1, 2, 3]
Map mapOf() ["k": v] {'k': v} { k: v }
Swift en résumé pour qui connaît Kotlin
let = val, var = var (identique)
nil = null, Type? = Type? (identique)
?? = ?: (valeur par défaut si nil)
if let = let block en Kotlin (déballage sûr)
func = fun, mais syntaxe d'appel différente (étiquettes)
struct = data class par défaut (plus simple)
async/await = identique aux coroutines Kotlin
"\(nom)" pour interpoler (au lieu de "$nom")
3. Xcode et le simulateur iOS
Xcode est l'environnement de développement officiel d'Apple. Il regroupe tout ce dont vous avez besoin
: éditeur de code Swift, simulateur iOS, outils de débogage, gestion des certificats, soumission à l'App
Store. C'est massif (environ 30 Go installé), mais c'est l'unique outil officiel et il est gratuit.
3.1. Installation
1. Ouvrez l'App Store sur votre Mac.
2. Recherchez « Xcode ».
3. Cliquez sur « Obtenir » (l'application est gratuite mais très grosse, prévoyez 1 à 3 heures de
téléchargement selon la connexion).
4. Au premier lancement, Xcode télécharge les composants supplémentaires (10 à 15 Go de plus).
5. Acceptez la licence Apple.
Versions de macOS et Xcode
Chaque version de Xcode requiert une version minimale de macOS. Xcode 16 (2025) nécessite macOS 14
Sonoma. Si votre Mac est ancien, vous serez bloqué sur une vieille version de Xcode, donc sur des SDK iOS
plus anciens. Vérifiez la compatibilité avant de prévoir un projet.
3.2. Création d'un projet
Lancez Xcode.
Cliquez sur « Create a new Xcode project ».
Sélectionnez l'onglet « iOS ».
Choisissez le template « App ».
Cliquez sur « Next » et remplissez :
Product Name : nom du projet (par exemple : MaPremiereApp)
Team : votre identifiant Apple (ou "None" pour un projet local)
Organization Identifier : identifiant unique inverse ([Link])
Interface : SwiftUI (par défaut)
Language : Swift
Cliquez sur « Next », choisissez l'emplacement, puis « Create ».
3.3. Structure d'un projet Xcode
MaPremiereApp/
├── [Link] ← Fichier de projet Xcode
└── MaPremiereApp/
├── [Link] ← Point d'entrée
├── [Link] ← Vue principale (à modifier)
├── [Link]/ ← Images, couleurs
│ └── [Link]/ ← Icônes de l'app
├── Preview Content/ ← Pour les Previews
└── [Link] ← Configuration (auto-géré)
Comparaison avec ce que vous connaissez :
Concept Android (Compose) Flutter RN (Expo) iOS (SwiftUI)
Point d'entrée [Link] [Link] [Link] [Link]
Vue principale setContent { ... } MaterialApp App component [Link]
Config dépendances [Link] [Link] [Link] [Link] / SPM
Ressources res/drawable/ assets/ assets/ [Link]/
3.4. Le fichier [Link] par défaut
Voici le code généré automatiquement quand vous créez un projet SwiftUI :
[Link] par défaut
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
Décomposition :
• import SwiftUI : importe le framework (comme import [Link].* en Compose).
• struct ContentView: View : définit une vue qui se conforme au protocole View (équivalent d'une
fonction @Composable).
• var body: some View : propriété obligatoire qui décrit le contenu visuel. some View = un type
qui implémente View.
• VStack { ... } : disposition verticale (équivalent Column).
• Image, Text : vues prédéfinies.
• #Preview { ... } : aperçu dans Xcode (équivalent @Preview Compose).
3.5. Le simulateur iOS
Le simulateur intégré à Xcode permet de tester votre app sans iPhone physique. Très complet :
• Simule différents modèles d'iPhone et d'iPad (iPhone SE, 15, Pro Max, etc.).
• Permet de simuler des gestes (rotation, multi-touch via clavier).
• Simule la localisation GPS, la prise de photo (à partir de fichiers).
• Performances très proches d'un vrai iPhone (compilation native pour la puce M du Mac).
Pour lancer votre app dans le simulateur :
6. Dans la barre en haut de Xcode, sélectionnez l'appareil cible (ex : iPhone 15).
7. Cliquez sur le bouton ▶(Play) ou appuyez sur Cmd+R.
8. Le simulateur s'ouvre et lance votre app.
9. Modifiez le code, sauvegardez (Cmd+S) — la Preview se met à jour instantanément.
Preview SwiftUI : la vraie magie
La Preview de Xcode est INCROYABLE. Quand vous écrivez du code SwiftUI, l'aperçu s'actualise en temps réel
sur le côté de votre éditeur — vous voyez le résultat sans même lancer le simulateur. Pour des modifications
simples (texte, couleur, taille), c'est plus rapide que le hot reload de Flutter ou Fast Refresh de React Native.
3.6. Tester sur un vrai iPhone
Pour tester sur un iPhone physique connecté à votre Mac :
Connectez l'iPhone au Mac avec un câble USB.
Dans Xcode, sélectionnez votre iPhone dans la liste des appareils.
Première fois : vous devez vous identifier avec votre Apple ID dans Xcode.
Vous devez aussi accepter l'appareil dans Réglages → Général → Gestion VPN et de l'appareil
sur l'iPhone.
Cliquez sur ▶— l'app est installée et lancée sur le téléphone.
4. Vues fondamentales
Les composants de base de SwiftUI s'appellent des « Views ». Comme en Compose, en Flutter ou en React
Native, tout est View. La syntaxe est très lisible : on enchaîne les vues avec des appels de méthodes pour
les configurer (les « modifiers », équivalents de Modifier en Compose).
4.1. Text — afficher du texte
Text
// Basique
Text("Bonjour SwiftUI")
// Avec modifiers
Text("Bonjour Aïcha")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.blue)
// Mise en forme avancée
Text("Un texte sur plusieurs lignes")
.font(.body)
.multilineTextAlignment(.center)
.lineLimit(2)
.truncationMode(.tail)
// Couleur via Color personnalisée
Text("Texte coloré")
.foregroundColor(Color(red: 0.12, green: 0.31, blue: 0.47))
// Interpolation (équivalent template string)
let nom = "Kofi"
Text("Bonjour \(nom)")
4.2. Image — afficher une image
Image
// Image système (icônes SF Symbols, fournies par Apple)
Image(systemName: "house")
Image(systemName: "[Link]")
.foregroundColor(.red)
.font(.system(size: 32))
// Image depuis [Link]
Image("monImage") // doit exister dans [Link]
// Image avec redimensionnement
Image("photo")
.resizable()
.scaledToFit()
.frame(width: 200, height: 150)
// Image depuis URL (depuis iOS 15)
AsyncImage(url: URL(string: "[Link] { phase in
if let image = [Link] {
[Link]().scaledToFit()
} else if [Link] != nil {
Text("Erreur de chargement")
} else {
ProgressView() // indicateur de chargement
}
}
.frame(width: 200, height: 200)
SF Symbols : 5000+ icônes natives
Apple fournit gratuitement SF Symbols : une bibliothèque de plus de 5000 icônes vectorielles cohérentes avec
le style iOS. Téléchargez l'app SF Symbols sur le Mac App Store pour les explorer. Aucune dépendance à
ajouter, aucune licence à payer. Équivalent direct des Material Icons de Compose / Flutter.
4.3. Button : boutons
Button
// Bouton simple
Button("Cliquer ici") {
print("Cliqué")
}
// Avec label custom
Button {
print("Cliqué")
} label: {
HStack {
Image(systemName: "[Link]")
Text("Favoris")
}
}
// Style prédéfini iOS 15+
Button("Action principale") {
// ...
}
.buttonStyle(.borderedProminent)
// Variantes :
// .buttonStyle(.bordered) — équivalent OutlinedButton
// .buttonStyle(.borderless) — équivalent TextButton
// .buttonStyle(.borderedProminent) — équivalent Button plein
// Bouton custom complet
Button {
print("Cliqué")
} label: {
Text("Mon bouton")
.frame(maxWidth: .infinity)
.padding()
.background([Link])
.foregroundColor(.white)
.cornerRadius(8)
}
// Bouton désactivé
Button("Indisponible") { }
.disabled(true)
4.4. TextField : saisie de texte
TextField
@State private var nom: String = ""
@State private var motDePasse: String = ""
// Champ basique
TextField("Nom", text: $nom)
.textFieldStyle(.roundedBorder)
.padding()
// Avec type de clavier
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.disableAutocorrection(true)
// Champ de mot de passe (texte masqué)
SecureField("Mot de passe", text: $motDePasse)
.textFieldStyle(.roundedBorder)
Notez le $ devant nom : il transforme la variable d'état en "binding" (lien bidirectionnel). On verra cela en
détail au chapitre sur l'état.
4.5. Le système des modifiers
Comme Modifier en Compose, SwiftUI applique des modifications via une chaîne d'appels :
Chaîne de modifiers
Text("Bonjour")
.font(.title) // taille de police
.foregroundColor(.white) // couleur du texte
.padding() // padding par défaut (16)
.background([Link]) // fond bleu
.cornerRadius(8) // coins arrondis
.shadow(radius: 4) // ombre
L'ordre des modifiers compte
Contrairement à ce qu'on pourrait penser, l'ordre des modifiers est important. .padding().background(.blue)
met le bleu autour du padding (donc le bleu déborde). .background(.blue).padding() met le padding autour du
fond bleu. Tester les deux ordres et observer la différence est très formateur.
4.6. Tableau des correspondances Vues
Compose Flutter React Native SwiftUI
Text("...") Text('...') <Text>...</Text> Text("...")
Image / AsyncImage [Link] <Image source={...}> Image / AsyncImage
Button { Text() } ElevatedButton <Pressable> Button("...") { ... }
TextField TextField <TextInput /> TextField / SecureField
Modifier Container/decoration style={...} modifiers chainés
[Link].X Icons.x Ionicons name="x" Image(systemName: "...")
5. Layouts : VStack, HStack, ZStack
Pour organiser plusieurs vues dans l'espace, SwiftUI propose trois conteneurs principaux. Leurs noms
commencent tous par « Stack » (pile), et le préfixe indique la direction.
5.1. VStack : pile verticale
VStack
VStack(alignment: .leading, spacing: 12) {
Text("Titre principal")
.font(.title)
Text("Sous-titre")
.font(.subheadline)
.foregroundColor(.secondary)
Image(systemName: "photo")
.font(.system(size: 48))
}
.padding()
Paramètres :
• alignment : alignement horizontal (.leading, .center, .trailing)
• spacing : espacement entre éléments en points
5.2. HStack : pile horizontale
HStack
HStack(alignment: .center, spacing: 8) {
Image(systemName: "[Link]")
.font(.title)
VStack(alignment: .leading) {
Text("Aïcha Diabaté")
.fontWeight(.bold)
Text("aicha@[Link]")
.font(.caption)
.foregroundColor(.gray)
}
Spacer() // pousse tout à gauche
Image(systemName: "[Link]")
.foregroundColor(.gray)
}
.padding()
Paramètres :
• alignment : alignement vertical (.top, .center, .bottom, .firstTextBaseline)
• spacing : espacement entre éléments
5.3. ZStack : superposition
ZStack
ZStack {
// Premier enfant : en bas
Rectangle()
.fill([Link])
.frame(width: 200, height: 100)
// Au-dessus
Text("Bonjour")
.foregroundColor(.white)
.fontWeight(.bold)
}
// Avec alignement spécifique
ZStack(alignment: .topTrailing) {
Image("photo")
// Badge en haut à droite
Text("NEW")
.padding(4)
.background([Link])
.foregroundColor(.white)
.cornerRadius(4)
.padding(8)
}
5.4. Spacer : répartir l'espace
Spacer pousse les vues autour de lui aux extrémités. Très utile dans HStack et VStack.
Spacer
// HStack avec Spacer entre deux éléments
HStack {
Text("À gauche")
Spacer()
Text("À droite")
}
// Pour aligner verticalement en bas
VStack {
Text("En haut")
Spacer()
Text("En bas")
}
// Plusieurs Spacers pour répartir
HStack {
Text("Début")
Spacer()
Text("Milieu")
Spacer()
Text("Fin")
}
5.5. Modifier .frame() pour les dimensions
Frame
// Taille fixe
Rectangle()
.frame(width: 100, height: 50)
// Taille maximale (équivalent fillMaxWidth en Compose)
Text("Pleine largeur")
.frame(maxWidth: .infinity)
.background([Link])
// Taille minimale et maximale
Text("Adaptatif")
.frame(minWidth: 100, maxWidth: 300)
// Hauteur fixe, largeur flexible
Rectangle()
.frame(maxWidth: .infinity, minHeight: 100)
5.6. Padding et background
Padding et background
Text("Élément stylé")
.padding() // padding par défaut (16)
.padding(.horizontal, 24) // padding spécifique
.padding(EdgeInsets(
top: 8, leading: 16,
bottom: 8, trailing: 16
))
.background(
RoundedRectangle(cornerRadius: 12)
.fill([Link])
)
.foregroundColor(.white)
5.7. Tableau de correspondances Layouts
Compose Flutter React Native SwiftUI
Column Column View flexDirection: VStack
'column'
Row Row View flexDirection: HStack
'row'
Box Stack View position: ZStack
'absolute'
verticalArrangement mainAxisAlignment justifyContent alignment / spacing
[Link](1f) Expanded flex: 1 Spacer()
[Link]([Link]) Padding padding: 16 .padding()
Spacer([Link]([Link])) SizedBox marginTop: 16 .padding(.top, 16)
fillMaxWidth() Container(width: width: '100%' .frame(maxWidth:
[Link]) .infinity)
5.8. Exemple complet : carte utilisateur
Exemple complet
struct CarteUtilisateur: View {
let nom: String
let email: String
let role: String
var body: some View {
HStack(spacing: 12) {
// Avatar
ZStack {
Circle()
.fill([Link])
.frame(width: 56, height: 56)
Text(String([Link] ?? "?"))
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
}
// Informations
VStack(alignment: .leading, spacing: 4) {
Text(nom)
.font(.headline)
Text(email)
.font(.caption)
.foregroundColor(.secondary)
Text(role)
.font(.caption2)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background([Link](0.2))
.foregroundColor(.green)
.cornerRadius(4)
}
Spacer()
Image(systemName: "[Link]")
.foregroundColor(.gray)
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(color: .[Link](0.1), radius: 4)
}
}
// Utilisation
CarteUtilisateur(
nom: "Aïcha Diabaté",
email: "aicha@[Link]",
role: "Administrateur"
)
6. Gestion d'état avec @State
SwiftUI gère l'état de manière déclarative et réactive : quand l'état change, la vue se reconstruit
automatiquement. Le mécanisme repose sur des annotations spéciales appelées « property wrappers »,
équivalentes des hooks React ou de mutableStateOf en Compose.
6.1. @State : l'état local
@State est l'équivalent direct de useState (React Native), remember + mutableStateOf (Compose), et de
la classe State en Flutter. Il s'utilise pour stocker un état mutable à l'intérieur d'une vue.
Compteur avec @State
struct Compteur: View {
// État local (équivalent useState ou remember/mutableStateOf)
@State private var valeur: Int = 0
var body: some View {
VStack(spacing: 16) {
Text("Valeur : \(valeur)")
.font(.largeTitle)
HStack(spacing: 12) {
Button("−") {
valeur -= 1
}
.buttonStyle(.bordered)
Button("+") {
valeur += 1
}
.buttonStyle(.borderedProminent)
}
}
.padding()
}
}
Décomposition :
• @State : annotation qui dit à SwiftUI : « cette variable est un état mutable, surveille-la et
reconstruis la vue quand elle change ».
• private : bonne pratique : l'état est local à cette vue, personne d'autre ne peut le modifier.
• var (pas let) : obligatoire car la valeur peut changer.
• valeur += 1 : écriture normale, SwiftUI détecte le changement automatiquement (pas besoin
d'un setState explicite).
Différence clé avec React Native
En React Native, vous écrivez setValeur(valeur + 1). En SwiftUI, vous écrivez juste valeur += 1. SwiftUI utilise la
"magie" des property wrappers pour intercepter les modifications. C'est plus naturel à écrire. Compose fait
pareil avec by mutableStateOf.
6.2. @Binding : partager un état avec une vue enfant
Quand vous voulez qu'une vue enfant puisse modifier un état défini dans le parent, vous utilisez @Binding.
C'est un lien bidirectionnel.
@Binding
struct ParametresView: View {
@State private var notifsActives: Bool = false
@State private var modeSombre: Bool = false
var body: some View {
VStack {
// On passe le binding avec $ devant la variable
BoutonSwitch(
titre: "Notifications",
actif: $notifsActives
)
BoutonSwitch(
titre: "Mode sombre",
actif: $modeSombre
)
}
}
}
struct BoutonSwitch: View {
let titre: String
@Binding var actif: Bool // lien vers le @State du parent
var body: some View {
HStack {
Text(titre)
Spacer()
Toggle("", isOn: $actif) // re-binding vers Toggle
.labelsHidden()
}
.padding()
}
}
Le "$" : c'est l'opérateur qui transforme un @State en Binding pour le passer à une vue enfant.
6.3. @StateObject et @ObservedObject — état partagé
Pour des objets complexes partagés entre plusieurs vues (équivalent ViewModel en Compose), SwiftUI
utilise les protocoles ObservableObject.
@StateObject et @ObservedObject
// Modèle (équivalent ViewModel)
class CompteurViewModel: ObservableObject {
@Published var valeur: Int = 0
func incrementer() {
valeur += 1
}
func reinitialiser() {
valeur = 0
}
}
// Vue qui CRÉE le ViewModel (premier endroit)
struct EcranCompteur: View {
@StateObject private var viewModel = CompteurViewModel()
var body: some View {
VStack {
Text("Valeur : \([Link])")
.font(.largeTitle)
HStack {
Button("+") {
[Link]()
}
Button("Reset") {
[Link]()
}
}
// Vue enfant qui OBSERVE le ViewModel
AffichageStatut(viewModel: viewModel)
}
}
}
// Vue enfant qui REÇOIT le ViewModel
struct AffichageStatut: View {
@ObservedObject var viewModel: CompteurViewModel
var body: some View {
if [Link] > 10 {
Text("Beaucoup !")
.foregroundColor(.red)
} else {
Text("Tranquille")
.foregroundColor(.green)
}
}
}
Règle simple :
• @StateObject : dans la vue qui CRÉE l'objet (une seule fois).
• @ObservedObject : dans les vues qui RECEVOIENT l'objet (n'importe combien).
• @Published : dans la classe, sur les propriétés à observer.
6.4. @Environment : accès à l'environnement
Pour accéder à des valeurs partagées par tout l'écosystème (mode sombre, taille de police accessibilité,
locale, etc.) :
@Environment
struct MaVue: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text("Mode actuel")
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
6.5. Comparaison des mécanismes d'état
Concept Compose Flutter RN SwiftUI
État local simple by setState useState @State
remember/mutableStateOf
Lier état parent/enfant fonction lambda callbacks props + @Binding
callbacks
État partagé viewModel() Provider Context API @StateObject
(ViewModel)
Observer (ViewModel) by viewModel() Consumer useContext @ObservedObject
Effet de bord LaunchedEffect initState useEffect .onAppear / .task
7. Listes : List et ForEach
SwiftUI propose deux composants principaux pour les listes : List pour les listes natives iOS (avec
apparence native, swipe pour supprimer, etc.), et ForEach pour itérer sur une collection à l'intérieur de
n'importe quelle vue.
7.1. List : la liste native iOS
List simple
struct EcranContacts: View {
let contacts = ["Aïcha", "Kofi", "Abla", "Yawo", "Esi"]
var body: some View {
List(contacts, id: \.self) { contact in
HStack {
Image(systemName: "[Link]")
Text(contact)
}
}
}
}
Le paramètre id: \.self dit à SwiftUI d'utiliser la valeur elle-même comme identifiant unique. C'est OK pour
des chaînes uniques, mais pour des objets complexes, on utilise un id propre.
7.2. List avec des structures
List avec structures
struct Produit: Identifiable {
let id = UUID() // Identifiant unique automatique
let nom: String
let prix: Double
}
struct EcranProduits: View {
let produits = [
Produit(nom: "Pomme", prix: 500),
Produit(nom: "Banane", prix: 200),
Produit(nom: "Mangue", prix: 750)
]
var body: some View {
List(produits) { produit in
HStack {
VStack(alignment: .leading) {
Text([Link])
.font(.headline)
Text("\([Link], specifier: "%.0f") FCFA")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
Image(systemName: "[Link]")
}
}
}
}
Le protocole Identifiable
Si votre type conforme au protocole Identifiable (avec une propriété id), vous n'avez pas besoin de spécifier
id: dans List. SwiftUI utilise automatiquement la propriété id. C'est très propre. UUID() crée automatiquement
un identifiant unique de type String.
7.3. Sections et en-têtes
List avec sections
List {
Section("Fruits") {
Text("Pomme")
Text("Banane")
Text("Mangue")
}
Section("Légumes") {
Text("Carotte")
Text("Tomate")
}
}
.listStyle(.insetGrouped)
Styles de liste disponibles :
• .plain : liste simple, sans groupes visuels.
• .grouped : liste groupée style iOS classique.
• .insetGrouped : style moderne avec coins arrondis.
• .sidebar : style barre latérale pour iPad.
7.4. Actions de swipe (glissement)
Une grande force de iOS : on peut glisser une ligne pour révéler des actions (supprimer, archiver…).
Swipe to delete
@State private var produits = [
"Pomme", "Banane", "Mangue"
]
List {
ForEach(produits, id: \.self) { produit in
Text(produit)
}
.onDelete { indexSet in
[Link](atOffsets: indexSet)
}
.onMove { source, destination in
[Link](fromOffsets: source, toOffset: destination)
}
}
7.5. ForEach — itérer dans n'importe quelle vue
Quand vous voulez afficher plusieurs éléments en dehors d'une List (par exemple dans une VStack ou une
grille), utilisez ForEach :
ForEach hors List
VStack(spacing: 8) {
ForEach(produits) { produit in
HStack {
Text([Link])
Spacer()
Text("\([Link], specifier: "%.0f")")
}
.padding()
.background([Link](0.1))
.cornerRadius(8)
}
}
7.6. ScrollView — vue défilable libre
ScrollView est utile pour des contenus longs qui ne sont pas des listes structurées :
ScrollView
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("Section 1")
.font(.title)
Text("Beaucoup de texte ici...")
Image("photo")
.resizable()
.scaledToFit()
Text("Section 2")
.font(.title)
Text("Encore du texte...")
}
.padding()
}
// Défilement horizontal
ScrollView(.horizontal) {
HStack {
ForEach(0..<20) { i in
Rectangle()
.fill([Link])
.frame(width: 100, height: 100)
}
}
.padding()
}
7.7. Grilles avec LazyVGrid et LazyHGrid
LazyVGrid
let colonnes = [
GridItem(.flexible()),
GridItem(.flexible())
]
ScrollView {
LazyVGrid(columns: colonnes, spacing: 16) {
ForEach(produits) { produit in
CarteProduit(produit: produit)
}
}
.padding()
}
// 3 colonnes de taille fixe
let colonnesFixes = [
GridItem(.fixed(100)),
GridItem(.fixed(100)),
GridItem(.fixed(100))
]
// Adaptatif : autant de colonnes que possible avec min 150
let colonnesAdaptatives = [
GridItem(.adaptive(minimum: 150))
]
7.8. Comparaison listes
Besoin Compose Flutter RN SwiftUI
Liste simple LazyColumn ListView ScrollView List
Liste perfo. LazyColumn (idem) [Link] FlatList List + ForEach
Grille LazyVerticalGrid [Link] FlatList numColumns LazyVGrid
Sections stickyHeader ListView + headers SectionList Section dans List
Swipe to delete (custom) Dismissible (custom) .onDelete (natif)
8. Formulaires et navigation
8.1. Form : conteneur de formulaire natif
Form est un conteneur spécial qui donne automatiquement à vos champs l'apparence iOS classique des
formulaires de réglages.
Form complet
struct EcranInscription: View {
@State private var nom: String = ""
@State private var email: String = ""
@State private var motDePasse: String = ""
@State private var notifications: Bool = false
@State private var langue: String = "Français"
var body: some View {
Form {
Section("Informations personnelles") {
TextField("Nom complet", text: $nom)
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.autocapitalization(.none)
SecureField("Mot de passe", text: $motDePasse)
}
Section("Préférences") {
Toggle("Recevoir les notifications", isOn: $notifications)
Picker("Langue", selection: $langue) {
Text("Français").tag("Français")
Text("English").tag("English")
Text("Ewé").tag("Ewé")
}
}
Section {
Button("S'inscrire") {
soumettre()
}
.frame(maxWidth: .infinity)
}
}
}
func soumettre() {
print("Inscription : \(nom), \(email)")
}
}
8.2. Validation manuelle
SwiftUI ne fournit pas de validateurs intégrés comme Flutter. On valide manuellement avec des propriétés
calculées :
Validation
struct FormulaireValide: View {
@State private var email: String = ""
@State private var motDePasse: String = ""
// Propriétés calculées pour validation
var emailValide: Bool {
[Link]("@") && [Link](".")
}
var motDePasseValide: Bool {
[Link] >= 6
}
var formulaireValide: Bool {
emailValide && motDePasseValide
}
var body: some View {
Form {
Section("Connexion") {
TextField("Email", text: $email)
if ![Link] && !emailValide {
Text("Email invalide")
.font(.caption)
.foregroundColor(.red)
}
SecureField("Mot de passe", text: $motDePasse)
if ![Link] && !motDePasseValide {
Text("Au moins 6 caractères")
.font(.caption)
.foregroundColor(.red)
}
}
Button("Se connecter") {
seConnecter()
}
.disabled(!formulaireValide)
}
}
func seConnecter() { /* ... */ }
}
8.3. Navigation avec NavigationStack
Pour la navigation entre écrans, SwiftUI propose NavigationStack (iOS 16+). C'est l'équivalent de NavHost
en Compose ou Navigator en Flutter.
Navigation basique
struct EcranAccueil: View {
var body: some View {
NavigationStack {
VStack(spacing: 16) {
// Lien direct vers une vue
NavigationLink("Voir le profil", destination: EcranProfil())
// Lien avec label custom
NavigationLink(destination: EcranParametres()) {
Label("Paramètres", systemImage: "gear")
}
}
.navigationTitle("Accueil")
}
}
}
struct EcranProfil: View {
var body: some View {
Text("Mon profil")
.navigationTitle("Profil")
.navigationBarTitleDisplayMode(.inline)
}
}
struct EcranParametres: View {
var body: some View {
Text("Mes paramètres")
.navigationTitle("Paramètres")
}
}
8.4. Navigation avec passage de données
Navigation avec données
struct Produit: Identifiable {
let id = UUID()
let nom: String
let prix: Double
}
struct EcranListe: View {
let produits: [Produit]
var body: some View {
NavigationStack {
List(produits) { produit in
// NavigationLink pour chaque ligne
NavigationLink(destination: EcranDetail(produit: produit)) {
HStack {
Text([Link])
Spacer()
Text("\([Link], specifier: "%.0f") FCFA")
.foregroundColor(.secondary)
}
}
}
.navigationTitle("Produits")
}
}
}
struct EcranDetail: View {
let produit: Produit
var body: some View {
VStack(spacing: 16) {
Text([Link])
.font(.largeTitle)
Text("\([Link], specifier: "%.0f") FCFA")
.font(.title)
.foregroundColor(.blue)
}
.navigationTitle("Détail")
}
}
8.5. Navigation programmatique
Parfois, on veut naviguer en code (par exemple après un appel API réussi) plutôt que via un lien :
Navigation programmatique
struct EcranLogin: View {
@State private var navigationPath = NavigationPath()
@State private var email: String = ""
var body: some View {
NavigationStack(path: $navigationPath) {
VStack {
TextField("Email", text: $email)
Button("Se connecter") {
// Navigation programmatique
[Link]("accueil")
}
}
.navigationDestination(for: [Link]) { route in
if route == "accueil" {
EcranAccueil()
}
}
}
}
}
8.6. TabView : onglets en bas
TabView
struct EcranPrincipal: View {
var body: some View {
TabView {
EcranAccueil()
.tabItem {
Label("Accueil", systemImage: "house")
}
EcranRecherche()
.tabItem {
Label("Recherche", systemImage: "magnifyingglass")
}
EcranPanier()
.tabItem {
Label("Panier", systemImage: "cart")
}
EcranProfil()
.tabItem {
Label("Profil", systemImage: "person")
}
}
}
}
8.7. Comparaison navigation
Concept Compose Flutter RN SwiftUI
Conteneur nav. NavHost Navigator [Link] NavigationStack
Bouton vers écran [Link] [Link] [Link] NavigationLink
Retour arrière popBackStack() [Link]() [Link]() (automatique)
Onglets bas NavigationBar (M3) BottomNavigationBar [Link] TabView
Titre TopAppBar AppBar(title:) [Link] .navigationTitle
9. Appels API avec URLSession et Codable
Pour communiquer avec une API REST, SwiftUI utilise URLSession (le client HTTP intégré à iOS) combiné
avec Codable (le système de sérialisation JSON automatique de Swift). C'est l'équivalent d'utiliser
fetch+[Link] en React Native, ou Retrofit+Gson en Compose.
9.1. Codable : sérialisation JSON automatique
Codable est un protocole qui permet à Swift de convertir automatiquement du JSON en objets et
inversement, sans aucune dépendance externe. C'est plus simple que Retrofit/Gson ou que créer des
fromJson en Flutter.
Structures Codable
// Structure conforme à Codable
struct Produit: Codable, Identifiable {
let id: Int
let title: String
let price: Double
let description: String
let category: String
let image: String
}
// Quand les noms JSON diffèrent des noms Swift
struct Utilisateur: Codable {
let id: Int
let nom: String
let dateInscription: Date
// Mapping JSON → Swift
enum CodingKeys: String, CodingKey {
case id
case nom = "user_name" // JSON: "user_name", Swift: "nom"
case dateInscription = "created_at" // JSON: "created_at", Swift: "dateInscription"
}
}
Codable = Gson + @SerializedName en un seul concept
En Compose, vous deviez ajouter Retrofit + Gson, puis annoter chaque champ avec @SerializedName. En
Swift, vous mettez juste : Codable sur votre struct, et c'est tout. Le mapping personnalisé se fait via
CodingKeys, qui est intégré au langage. Beaucoup plus élégant.
9.2. Appel API basique avec async/await
Appel API basique
func chargerProduits() async throws -> [Produit] {
// 1. Créer l'URL
let url = URL(string: "[Link]
// 2. Faire l'appel HTTP
let (data, _) = try await [Link](from: url)
// 3. Décoder le JSON en tableau de Produit
let produits = try JSONDecoder().decode([Produit].self, from: data)
return produits
}
Comparaison avec Retrofit en Compose :
Comparaison Retrofit / URLSession
// Compose (Kotlin)
interface ProduitApi {
@GET("products")
suspend fun getProduits(): List<Produit>
}
// ... + configuration Retrofit
val produits = [Link]()
// SwiftUI (Swift)
let url = URL(string: "[Link]
let (data, _) = try await [Link](from: url)
let produits = try JSONDecoder().decode([Produit].self, from: data)
Notez qu'en SwiftUI, on n'a pas besoin de définir une interface séparée. Tout se fait directement, avec
moins de boilerplate.
9.3. ViewModel + appel API dans une vue
Vue complète avec ViewModel
// ViewModel
@MainActor
class ProduitViewModel: ObservableObject {
@Published var produits: [Produit] = []
@Published var chargement: Bool = false
@Published var erreur: String? = nil
func chargerProduits() async {
chargement = true
erreur = nil
do {
let url = URL(string: "[Link]
let (data, _) = try await [Link](from: url)
produits = try JSONDecoder().decode([Produit].self, from: data)
} catch {
erreur = [Link]
}
chargement = false
}
}
// Vue qui consomme le ViewModel
struct EcranProduits: View {
@StateObject private var viewModel = ProduitViewModel()
var body: some View {
NavigationStack {
Group {
if [Link] {
ProgressView("Chargement...")
} else if let erreur = [Link] {
VStack {
Image(systemName: "[Link]")
.font(.largeTitle)
.foregroundColor(.red)
Text("Erreur : \(erreur)")
Button("Réessayer") {
Task {
await [Link]()
}
}
}
} else {
List([Link]) { produit in
VStack(alignment: .leading) {
Text([Link])
.font(.headline)
Text("\([Link], specifier: "%.2f") $")
.foregroundColor(.secondary)
}
}
}
}
.navigationTitle("Produits")
.task {
await [Link]()
}
}
}
}
Points importants :
• @MainActor : garantit que les mises à jour de @Published se font sur le thread principal
(équivalent du dispatcher Main en Kotlin).
• .task { ... } : modifier SwiftUI qui lance une tâche async quand la vue apparaît (équivalent
LaunchedEffect en Compose, useEffect en RN).
• Task { ... } : lance une coroutine depuis un contexte non-async (comme un bouton).
9.4. Méthodes HTTP POST, PUT, DELETE
POST
func creerProduit(_ produit: Produit) async throws -> Produit {
let url = URL(string: "[Link]
var request = URLRequest(url: url)
[Link] = "POST"
[Link]("application/json", forHTTPHeaderField: "Content-Type")
// Encoder l'objet en JSON
[Link] = try JSONEncoder().encode(produit)
let (data, response) = try await [Link](for: request)
// Vérifier le code HTTP
if let httpResponse = response as? HTTPURLResponse,
[Link] == 201 {
return try JSONDecoder().decode([Link], from: data)
} else {
throw URLError(.badServerResponse)
}
}
9.5. Charger une image depuis une URL
SwiftUI fournit AsyncImage natif pour charger une image depuis une URL, exactement comme Coil en
Compose ou [Link] en Flutter.
AsyncImage
AsyncImage(url: URL(string: [Link])) { image in
image
.resizable()
.scaledToFit()
} placeholder: {
ProgressView() // affiché pendant le chargement
}
.frame(width: 200, height: 200)
// Avec gestion d'erreur explicite
AsyncImage(url: URL(string: [Link])) { phase in
switch phase {
case .empty:
ProgressView()
case .success(let image):
[Link]().scaledToFit()
case .failure:
Image(systemName: "photo")
.foregroundColor(.gray)
@unknown default:
EmptyView()
}
}
.frame(width: 200, height: 200)
9.6. Comparaison appels API
Aspect Compose Flutter RN SwiftUI
Client HTTP Retrofit + OkHttp http / dio fetch (natif) URLSession (natif)
Sérialisation Gson + @Serialized json_serializable [Link] Codable (natif)
Asynchrone suspend + coroutines Future async async/await async/await
Bibliothèque ext. Oui (Retrofit) Oui (http/dio) Non Non
Image réseau Coil (à ajouter) [Link] <Image source> AsyncImage (natif)
iOS = beaucoup moins de dépendances
Vous remarquerez qu'en iOS/SwiftUI, presque tout est natif : URLSession pour HTTP, Codable pour JSON,
AsyncImage pour les images. Pas besoin d'installer 3 bibliothèques comme avec Compose. C'est un gros
avantage : moins de poids, moins de risques de bugs liés aux mises à jour.
10. Publication sur l'App Store
Publier une application iOS sur l'App Store est un processus plus formel que sur Google Play. Apple insiste
sur la qualité, la sécurité et l'expérience utilisateur. Voici les étapes pour vous donner une vue d'ensemble
du processus professionnel.
10.1. Pré-requis
• Compte Apple Developer Program : 99 $/an, à souscrire sur [Link].
• Un Mac avec Xcode : dernière version recommandée.
• Une application finie et testée : sans crash, conforme aux guidelines Apple.
• Icônes et captures d'écran : conformes aux dimensions exigées.
• Description de l'app et mots-clés : pour le référencement App Store.
• Politique de confidentialité : OBLIGATOIRE, même pour une app gratuite. URL ou texte exigé.
10.2. Préparer l'application dans Xcode
Sélectionnez le projet → onglet « General ».
Mettez à jour le Bundle Identifier (ex : [Link]). Doit être unique mondialement.
Définissez la version (ex : 1.0.0) et le build number (ex : 1).
Ajoutez l'icône de l'app dans [Link] → AppIcon (toutes les tailles : 1024x1024, 180x180,
etc.).
Sélectionnez votre équipe Apple Developer dans « Signing & Capabilities ».
Choisissez « Generic iOS Device » comme cible (pas un simulateur).
Menu Product → Archive.
Xcode compile et crée une archive prête à être envoyée.
10.3. Soumettre via App Store Connect
App Store Connect ([Link]) est le portail web où vous gérez vos apps :
Connectez-vous avec votre Apple ID développeur.
My Apps → bouton +. Nouvelle App iOS.
Remplissez : nom de l'app (apparaîtra sur l'App Store), langues, catégorie, prix.
Téléversez votre archive depuis Xcode (Window → Organizer → Distribute App).
Dans App Store Connect, remplissez les informations :
Description de l'app (4000 caractères max).
Mots-clés (100 caractères max, séparés par virgule).
URL de support.
URL de politique de confidentialité.
Captures d'écran pour différentes tailles d'iPhone et d'iPad.
Classification de contenu (par âge).
Soumettez pour revue avec le bouton « Submit for Review ».
10.4. Délais et coûts
Étape Délai typique Coût
Création compte développeur 1-3 jours (vérification Apple) 99 $/an
Préparation projet 1-7 jours selon complexité 0$
Archive et upload 30 minutes 0$
Revue Apple 24-72 heures (parfois moins) 0$
Re-soumission si rejet +1-3 jours 0$
Publication Immédiat après acceptation 0 $ ou commission Apple
Commission Apple (achats) 15-30 % de chaque vente 15-30 %
10.5. Causes fréquentes de rejet
• Crashes : l'app plante pendant les tests d'Apple. Testez exhaustivement avant.
• Permissions injustifiées : demander la caméra ou le GPS sans raison claire dans l'app.
• Métadonnées incorrectes : captures d'écran qui ne représentent pas l'app réelle.
• Bouton restaurer manquant : si l'app a des achats in-app, un bouton « Restaurer mes achats »
est obligatoire.
• Connexion par tiers obligatoire : si vous proposez Sign in with Google, vous devez aussi
proposer Sign in with Apple.
• Contenu inapproprié : violence, contenu adulte, fausses informations.
• Plagiat ou clone d'app existante.
10.6. Comparaison Google Play / App Store
Aspect Google Play App Store
Coût compte 25 $ à vie 99 $/an
Validation Automatique + humaine 100 % humaine
Délai validation Quelques heures 1-3 jours
Taux de rejet ~10 % ~20-30 %
Commission ventes 15-30 % 15-30 %
Politique de confidentialité Obligatoire Obligatoire
Mises à jour Très rapides Re-validation à chaque fois
La commission Apple : un sujet sensible
Apple prélève 30 % sur la plupart des ventes (15 % pour les petits développeurs avec moins d'1 million de
$/an de revenus). Cette commission concerne les achats in-app, les abonnements, et les apps payantes — pas
les apps gratuites avec services externes. C'est un point à prendre en compte dans votre business model.
11. Tableau de correspondance final
Vous maîtrisez désormais les QUATRE grands frameworks de développement mobile moderne : Compose
(natif Android), Flutter (multi-plateforme), React Native (natif via JS), et SwiftUI (natif Apple). Voici le
tableau final que vous pouvez conserver comme référence.
11.1. Concepts généraux
Concept Compose Flutter React Native SwiftUI
Langage Kotlin Dart JS / TS Swift
UI @Composable fun Widget function struct View
déclarative Component()
État local remember + setState useState @State
mutableStateOf
État partagé ViewModel Provider useContext @StateObject /
@ObservedObject
Effet de bord LaunchedEffect initState useEffect .task / .onAppear
Constante val final const let
Variable var var let var
Null-safety String? String? (rien) String?
11.2. Layouts
Concept Compose Flutter RN SwiftUI
Vertical Column Column View column VStack
Horizontal Row Row View row HStack
Superposition Box Stack View absolute ZStack
Padding [Link] Padding/Container padding: .padding()
Marge fixe Spacer SizedBox marginTop .padding(.top)
Étendre [Link] Expanded flex: 1 Spacer()
Centrer [Link] Center justify+align center alignment / center
11.3. Composants UI
Élément Compose Flutter RN SwiftUI
Texte Text("...") Text('...') <Text> Text("...")
Image AsyncImage / Coil [Link] <Image source> AsyncImage
Bouton Button { Text } ElevatedButton <Pressable> Button("...") { }
Saisie TextField TextField <TextInput> TextField /
SecureField
Liste LazyColumn [Link] FlatList List + ForEach
longue
Grille LazyVerticalGrid GridView FlatList LazyVGrid
numColumns
Indicateur CircularProgressIndicator CircularProgressIndicator ActivityIndicator ProgressView
charge.
Icône [Link].X Icons.x Ionicons Image(systemName:)
11.4. Backend et persistance
Besoin Compose Flutter RN SwiftUI
HTTP Retrofit + OkHttp http / dio fetch / axios URLSession
JSON parsing Gson / kotlinx json_serializable [Link] Codable
Asynchrone Coroutines Future + async Promise + async async / await
BD locale Room sqflite WatermelonDB Core Data / SwiftData
Préférences DataStore shared_prefs AsyncStorage UserDefaults
Navigation Nav. Compose Navigator React Navigation NavigationStack
11.5. Plateformes et déploiement
Aspect Compose Flutter RN SwiftUI
Android Oui (cible naturelle) Oui Oui Non
iOS Non (KMP partiel) Oui Oui Oui (cible naturelle)
Web Compose for Web Flutter Web React Native Web Non
Desktop Compose Desktop Flutter Desktop Limité macOS natif
watchOS Non Non Non Oui
IDE principal Android Studio Android Studio / VS Code VS Code Xcode
Coût publication 25 $ une fois Selon stores cibles Selon stores cibles 99 $/an
Synthèse : quel framework pour quel projet ?
• Application Android premium uniquement : Jetpack Compose. Performances et intégration
optimales.
• Application iOS premium uniquement : SwiftUI. Code minimal, intégration parfaite avec
écosystème Apple.
• Application Android + iOS avec UI custom : Flutter. Une seule base de code, performances
graphiques excellentes.
• Application Android + iOS avec look natif : React Native. Composants natifs réels, équipe
JavaScript.
• Web + mobile partagé : React + React Native, ou Flutter Web + Flutter Mobile.
Votre profil après cette formation
Vous êtes maintenant à l'intersection rare de quatre écosystèmes mobiles majeurs. Sur le marché du
travail, ce profil est très recherché et vous ouvre des opportunités dans :
• Startups multi-plateformes (Flutter ou React Native sont privilégiés).
• Grandes entreprises avec apps natives séparées (Compose côté Android, SwiftUI côté iOS).
• Consulting et freelance (capacité à choisir le bon outil pour chaque projet).
• Création de votre propre application (vous comprenez les coûts, contraintes et performances de
chaque option).