Introduction : pourquoi SwiftData change tout pour la persistance iOS
Si vous avez déjà travaillé avec Core Data, vous connaissez la chanson : un fichier .xcdatamodeld à maintenir, des sous-classes de NSManagedObject à générer, un stack de persistance à configurer manuellement, et des @FetchRequest parfois capricieux. Bref, ça fonctionne — mais avouons-le, c'est rarement agréable.
SwiftData, présenté par Apple à la WWDC 2023, change radicalement la donne. Ce framework de persistance, construit par-dessus Core Data, remplace tout le boilerplate par des macros Swift élégantes et une intégration native avec SwiftUI. Le résultat ? Définir un modèle de données, sauvegarder, interroger et synchroniser via iCloud demande une fraction du code qu'il fallait avant. Honnêtement, la première fois que j'ai migré un modèle Core Data vers SwiftData, j'ai été surpris de supprimer autant de lignes.
Avec iOS 26 (annoncé à la WWDC 2025), SwiftData franchit un nouveau cap grâce au support de l'héritage de classes et à la correction de bugs critiques rétrocompatibles jusqu'à iOS 17.
Dans ce guide, on couvre tout : de la création d'un modèle à la migration de schéma, en passant par les requêtes avancées, les relations et la synchronisation CloudKit. Avec des exemples de code fonctionnels à chaque étape. Allons-y.
Comprendre l'architecture de SwiftData
Avant de plonger dans le code, prenons un moment pour comprendre les trois piliers de SwiftData. L'architecture est simple (vraiment), mais la maîtriser vous évitera bien des maux de tête par la suite.
Le trio fondamental : ModelContainer, ModelContext et @Model
SwiftData s'articule autour de trois composants :
- @Model : la macro qui transforme une classe Swift ordinaire en modèle persistant. Elle remplace
NSManagedObjectet le fichier.xcdatamodeld. - ModelContainer : le « backend » de persistance. Il crée et gère la base de données SQLite sous-jacente — c'est l'équivalent de
NSPersistentContainer. - ModelContext : l'espace de travail en mémoire. Il suit les objets créés, modifiés et supprimés avant de les sauvegarder. Pensez-y comme l'équivalent de
NSManagedObjectContext.
En pratique, votre app crée un ModelContainer au démarrage, qui fournit automatiquement un ModelContext principal (le « main context ») accessible dans toutes vos vues SwiftUI via l'environnement. Pas besoin de câblage supplémentaire.
Différence avec Core Data
Voici ce qui change concrètement :
- Plus de fichier .xcdatamodeld — le schéma est défini directement dans votre code Swift
- Plus de NSManagedObject — vos modèles sont de simples classes Swift annotées
@Model - Sauvegarde automatique — SwiftData sauvegarde implicitement lors d'événements du cycle de vie de l'UI
- Observable par défaut — les modèles se conforment automatiquement à
Observable,IdentifiableetHashable
C'est un sacré gain de temps, surtout quand on démarre un nouveau projet.
Définir un modèle avec @Model
Commençons par le commencement : créer un modèle de données. La macro @Model est tout ce dont vous avez besoin :
import SwiftData
@Model
class Tache {
var titre: String
var estTerminee: Bool
var dateCreation: Date
var notes: String?
init(titre: String, estTerminee: Bool = false, notes: String? = nil) {
self.titre = titre
self.estTerminee = estTerminee
self.dateCreation = .now
self.notes = notes
}
}
C'est tout. Vraiment. Pas de fichier de modèle séparé, pas de génération de code. SwiftData déduit automatiquement le schéma de persistance à partir des propriétés de votre classe. Les types supportés incluent String, Int, Double, Bool, Date, Data, URL, ainsi que tout type Codable (structs, enums).
Personnaliser les propriétés avec @Attribute
La macro @Attribute permet d'ajouter des contraintes à vos propriétés :
@Model
class Utilisateur {
@Attribute(.unique) var email: String
var nom: String
@Attribute(.externalStorage) var avatar: Data?
@Attribute(.encrypt) var tokenSecret: String?
init(email: String, nom: String) {
self.email = email
self.nom = nom
}
}
Les options les plus utiles :
.unique— garantit l'unicité de la valeur (avec upsert automatique en cas de doublon, ce qui est plutôt malin).externalStorage— stocke les données volumineuses (images, fichiers) en dehors de la base SQLite.encrypt— chiffre la propriété dans le store persistant.spotlight— rend la propriété indexable par Spotlight
Propriétés transitoires
Si vous avez des propriétés calculées ou temporaires que vous ne voulez pas persister, utilisez @Transient :
@Model
class Article {
var titre: String
var contenu: String
@Transient var nombreDeMots: Int {
contenu.split(separator: " ").count
}
init(titre: String, contenu: String) {
self.titre = titre
self.contenu = contenu
}
}
Configurer le ModelContainer dans votre app
Pour que SwiftData fonctionne, votre application doit disposer d'au moins un ModelContainer. La méthode la plus simple passe par le modifier .modelContainer sur votre WindowGroup.
Configuration basique
import SwiftUI
import SwiftData
@main
struct MonApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Tache.self)
}
}
Cette seule ligne crée la base de données, configure le container et injecte un ModelContext principal dans l'environnement SwiftUI. Toutes les vues enfants y auront accès automatiquement — on est loin du boilerplate de Core Data.
Configuration avancée avec ModelConfiguration
Pour un contrôle plus fin (stockage en mémoire pour les tests, CloudKit, etc.), utilisez ModelConfiguration :
@main
struct MonApp: App {
let container: ModelContainer
init() {
do {
let config = ModelConfiguration(
"MaBase",
isStoredInMemoryOnly: false,
allowsSave: true,
cloudKitDatabase: .automatic
)
container = try ModelContainer(
for: Tache.self, Utilisateur.self,
configurations: config
)
} catch {
fatalError("Impossible de créer le ModelContainer : \(error)")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
Quelques options utiles de ModelConfiguration :
isStoredInMemoryOnly: true— parfait pour les previews et les tests unitairescloudKitDatabase: .automatic— active la synchronisation iCloudallowsSave: false— mode lecture seule (pratique pour certains cas de debug)
Activer le undo/redo
SwiftData supporte nativement l'annulation et le rétablissement, et c'est aussi simple que ça :
.modelContainer(for: Tache.self, isUndoEnabled: true)
Opérations CRUD : créer, lire, modifier, supprimer
Toutes les opérations sur les données passent par le ModelContext. Dans une vue SwiftUI, vous y accédez via l'environnement :
@Environment(\.modelContext) private var context
Voyons chaque opération en détail.
Créer (Insert)
func ajouterTache(titre: String) {
let nouvelleTache = Tache(titre: titre)
context.insert(nouvelleTache)
// Pas besoin d'appeler save() — SwiftData sauvegarde automatiquement
}
SwiftData utilise une sauvegarde implicite qui se déclenche lors d'événements du cycle de vie de l'UI (changement de scène, timer interne). Dans la majorité des cas, vous n'avez pas besoin d'appeler context.save() manuellement. C'est un vrai confort.
Lire (Query)
La macro @Query est le moyen le plus simple de récupérer des données dans une vue SwiftUI :
struct ListeTaches: View {
@Query(sort: \Tache.dateCreation, order: .reverse)
private var taches: [Tache]
var body: some View {
List(taches) { tache in
HStack {
Text(tache.titre)
Spacer()
if tache.estTerminee {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
}
}
}
}
}
@Query se met à jour automatiquement chaque fois que vos données changent, et votre vue SwiftUI se rafraîchit en conséquence. C'est la magie de l'intégration native — et franchement, ça change la vie par rapport aux @FetchRequest de Core Data.
Modifier (Update)
Modifier un objet SwiftData est d'une simplicité presque déconcertante. Vous modifiez directement les propriétés :
func basculerÉtat(tache: Tache) {
tache.estTerminee.toggle()
// C'est tout ! SwiftData détecte et sauvegarde le changement automatiquement
}
Pas de save(), pas de objectWillChange. SwiftData suit les modifications en interne grâce au protocole Observable et les sauvegarde lors du prochain cycle de persistance.
Supprimer (Delete)
func supprimer(tache: Tache) {
context.delete(tache)
}
// Suppression depuis une liste avec swipe
func supprimerSélection(_ indexSet: IndexSet) {
for index in indexSet {
context.delete(taches[index])
}
}
Requêtes avancées avec #Predicate et FetchDescriptor
La macro @Query est pratique pour les cas simples, mais quand les choses se corsent, SwiftData propose #Predicate et FetchDescriptor. Et c'est là que ça devient vraiment intéressant.
Filtrer avec #Predicate
#Predicate est le remplaçant moderne de NSPredicate. Il est typé, vérifié à la compilation et s'écrit en Swift natif — fini les chaînes de format obscures :
// Tâches non terminées, triées par date
@Query(
filter: #Predicate { tache in
tache.estTerminee == false
},
sort: \Tache.dateCreation,
order: .reverse
)
private var tachesEnCours: [Tache]
Vous pouvez combiner des conditions :
@Query(
filter: #Predicate { tache in
!tache.estTerminee && tache.titre.contains("urgent")
},
sort: \Tache.dateCreation
)
private var tachesUrgentes: [Tache]
Attention : les comparaisons de chaînes comme contains() et starts(with:) sont sensibles à la casse. Et petit piège à connaître : les méthodes uppercased() et lowercased() ne sont pas supportées dans les prédicats.
FetchDescriptor pour un contrôle total
Pour les requêtes programmatiques (en dehors de @Query), utilisez FetchDescriptor avec le ModelContext :
func rechercherTaches(contenant texte: String) throws -> [Tache] {
let prédicat = #Predicate { tache in
tache.titre.contains(texte)
}
var descripteur = FetchDescriptor(
predicate: prédicat,
sortBy: [SortDescriptor(\Tache.dateCreation, order: .reverse)]
)
descripteur.fetchLimit = 20
return try context.fetch(descripteur)
}
FetchDescriptor offre des options que @Query ne propose pas directement :
fetchLimit— limiter le nombre de résultatsfetchOffset— paginationincludePendingChanges— inclure ou exclure les changements non sauvegardésrelationshipKeyPathsForPrefetching— préchargement des relations (très utile pour éviter le N+1)
Requêtes dynamiques dans les vues
Un cas courant : filtrer les données en fonction d'un état de l'UI (barre de recherche, segment control). Le piège, c'est que @Query ne peut pas être modifié dynamiquement après l'initialisation de la vue.
La solution ? Injecter le filtre via l'initialiseur :
struct ListeTachesFiltree: View {
@Query private var taches: [Tache]
init(montrerTerminees: Bool) {
let filtre = #Predicate { tache in
montrerTerminees || !tache.estTerminee
}
_taches = Query(filter: filtre, sort: \Tache.dateCreation)
}
var body: some View {
List(taches) { tache in
Text(tache.titre)
}
}
}
Depuis la vue parente, vous passez simplement le paramètre :
struct ContentView: View {
@State private var montrerTerminees = false
var body: some View {
ListeTachesFiltree(montrerTerminees: montrerTerminees)
}
}
C'est un pattern que je me retrouve à utiliser constamment dans mes projets SwiftUI.
Gérer les relations entre modèles
Les applications réelles ne tournent jamais autour d'un seul modèle. SwiftData gère les relations entre modèles de manière plutôt élégante, que ce soit du one-to-many ou du many-to-many.
Relations one-to-many
Prenons l'exemple classique d'un projet contenant des tâches :
@Model
class Projet {
var nom: String
var dateCreation: Date
@Relationship(deleteRule: .cascade, inverse: \Tache.projet)
var taches: [Tache]
init(nom: String) {
self.nom = nom
self.dateCreation = .now
self.taches = []
}
}
@Model
class Tache {
var titre: String
var estTerminee: Bool
var projet: Projet?
init(titre: String, projet: Projet? = nil) {
self.titre = titre
self.estTerminee = false
self.projet = projet
}
}
Points importants :
@Relationship(deleteRule: .cascade)— supprimer un projet supprime automatiquement toutes ses tâchesinverse: \Tache.projet— rend la relation bidirectionnelle explicite- Les relations sont chargées en lazy loading — SwiftData ne charge les tâches d'un projet que lorsque vous y accédez réellement
Les règles de suppression
SwiftData propose quatre règles de suppression (et choisir la bonne est plus important qu'on ne le pense) :
.nullify(par défaut) — met la référence inverse ànil.cascade— supprime tous les objets liés en cascade.deny— empêche la suppression si des objets liés existent.noAction— ne fait rien (à utiliser avec prudence, voire pas du tout)
Relations many-to-many
Pour les relations many-to-many (par exemple des articles avec des étiquettes), les deux côtés utilisent des tableaux :
@Model
class ArticleBlog {
var titre: String
@Relationship(inverse: \Etiquette.articles)
var etiquettes: [Etiquette]
init(titre: String) {
self.titre = titre
self.etiquettes = []
}
}
@Model
class Etiquette {
var nom: String
var articles: [ArticleBlog]
init(nom: String) {
self.nom = nom
self.articles = []
}
}
Important : SwiftData n'infère pas automatiquement les relations many-to-many. Vous devez les déclarer explicitement avec @Relationship. Si vous oubliez, la relation ne fonctionnera que dans un sens — et c'est le genre de bug qui peut vous faire perdre un après-midi entier.
Migrations de schéma avec VersionedSchema
Votre modèle de données va évoluer. C'est inévitable. La bonne nouvelle, c'est que SwiftData propose un système de migration assez robuste basé sur deux protocoles : VersionedSchema et SchemaMigrationPlan.
Migrations légères (automatiques)
SwiftData gère automatiquement les changements simples sans code supplémentaire :
- Ajouter une propriété optionnelle ou avec valeur par défaut
- Renommer une propriété (avec
@Attribute(originalName:)) - Supprimer une propriété
- Modifier le type d'une relation
Pour ces cas-là, vous n'avez strictement rien à faire. SwiftData s'en occupe.
Migrations personnalisées
Pour les changements plus complexes, il faut définir des versions de schéma. Ça demande un peu plus de travail, mais ça reste bien plus lisible que les mappings Core Data :
// Version 1 : schéma initial
enum SchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[Tache.self]
}
@Model
class Tache {
var titre: String
var estTerminee: Bool
init(titre: String) {
self.titre = titre
self.estTerminee = false
}
}
}
// Version 2 : ajout d'une priorité
enum SchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] {
[Tache.self]
}
@Model
class Tache {
var titre: String
var estTerminee: Bool
var priorite: Int
init(titre: String, priorite: Int = 0) {
self.titre = titre
self.estTerminee = false
self.priorite = priorite
}
}
}
Ensuite, définissez le plan de migration :
enum PlanMigrationTaches: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self
) { context in
// Logique de migration personnalisée
let taches = try context.fetch(FetchDescriptor())
// Traitement des données existantes si nécessaire
} didMigrate: { context in
try context.save()
}
}
Conseil important : définissez toujours votre schéma initial dans un VersionedSchema, même si vous n'avez pas encore de migration à effectuer. Croyez-moi, votre futur vous vous en remerciera quand il sera temps de faire évoluer le schéma.
Synchronisation iCloud avec CloudKit
L'un des avantages majeurs de SwiftData est sa synchronisation transparente avec iCloud. Et la configuration est étonnamment simple.
Prérequis
- Activer la capability iCloud dans votre target Xcode, avec CloudKit coché
- Ajouter un container CloudKit
- Activer la capability Background Modes et cocher « Remote Notifications »
Adapter vos modèles pour CloudKit
CloudKit impose des contraintes spécifiques sur vos modèles SwiftData — et c'est là que ça se complique un petit peu :
- Toutes les propriétés doivent être optionnelles ou avoir une valeur par défaut
- Toutes les relations doivent être optionnelles
- Pas de contrainte
.unique— CloudKit ne les supporte pas
// Modèle compatible CloudKit
@Model
class TacheCloud {
var titre: String = ""
var estTerminee: Bool = false
var dateCreation: Date = .now
var notes: String?
var projet: ProjetCloud?
init(titre: String) {
self.titre = titre
}
}
Ensuite, configurez le container avec CloudKit :
let config = ModelConfiguration(
cloudKitDatabase: .automatic
)
let container = try ModelContainer(
for: TacheCloud.self,
configurations: config
)
Et voilà. SwiftData synchronisera automatiquement les données entre les appareils du même compte iCloud.
Limitation importante à garder en tête : SwiftData ne supporte actuellement que la synchronisation de données privées. Le partage de données entre différents Apple IDs (bases partagées ou publiques CloudKit) n'est pas encore pris en charge. Si vous avez besoin de cette fonctionnalité, Core Data reste malheureusement votre seule option.
Nouveauté iOS 26 : l'héritage de modèles
La grande nouveauté de SwiftData dans iOS 26, annoncée à la WWDC 2025, c'est le support de l'héritage de classes. Avant iOS 26, les modèles SwiftData ne pouvaient tout simplement pas hériter les uns des autres — une limitation qui frustrait pas mal de développeurs habitués aux hiérarchies de modèles Core Data.
Comment ça fonctionne
Vous pouvez maintenant définir une classe de base et créer des sous-classes qui héritent de ses propriétés :
@available(iOS 26, *)
@Model
class Voyage {
var destination: String
var dateDebut: Date
var dateFin: Date
init(destination: String, dateDebut: Date, dateFin: Date) {
self.destination = destination
self.dateDebut = dateDebut
self.dateFin = dateFin
}
}
@available(iOS 26, *)
@Model
class VoyageAffaires: Voyage {
var entreprise: String
var budget: Double
init(destination: String, dateDebut: Date, dateFin: Date,
entreprise: String, budget: Double) {
self.entreprise = entreprise
self.budget = budget
super.init(destination: destination, dateDebut: dateDebut, dateFin: dateFin)
}
}
@available(iOS 26, *)
@Model
class VoyagePersonnel: Voyage {
var activites: [String]
init(destination: String, dateDebut: Date, dateFin: Date,
activites: [String] = []) {
self.activites = activites
super.init(destination: destination, dateDebut: dateDebut, dateFin: dateFin)
}
}
Requêtes polymorphes avec « is »
iOS 26 permet aussi d'utiliser le mot-clé is dans les prédicats pour filtrer par type. C'est le genre de fonctionnalité qui manquait cruellement :
@available(iOS 26, *)
@Query(filter: #Predicate { voyage in
voyage is VoyageAffaires
})
var voyagesAffaires: [Voyage]
Important : l'héritage de modèles est exclusivement disponible sur iOS 26+. Toute utilisation doit être annotée avec @available(iOS 26, *). Si votre cible de déploiement minimale est antérieure, vous ne pourrez pas en profiter pour le moment.
Corrections de bugs rétrocompatibles
Bonne nouvelle quand même : Xcode 26 a aussi corrigé des bugs critiques qui sont rétrocompatibles jusqu'à iOS 17 :
- Les mises à jour de vues fonctionnent maintenant correctement lors de la mutation de données sous
@ModelActor - Les propriétés de type
Codablepeuvent désormais être utilisées dans les prédicats (enfin !)
SwiftData vs Core Data : que choisir en 2026 ?
La question revient constamment : faut-il utiliser SwiftData ou Core Data ? La réponse dépend de votre contexte.
Choisissez SwiftData si :
- Vous démarrez un nouveau projet ciblant iOS 17+
- Votre application est de complexité simple à modérée
- Vous développez avec SwiftUI (l'intégration est incomparable)
- Vous privilégiez la rapidité de développement et la lisibilité du code
- Vous n'avez besoin que de la synchronisation iCloud privée
Restez sur Core Data si :
- Votre app doit supporter des appareils antérieurs à iOS 17
- Vous avez besoin de fonctionnalités avancées :
NSFetchedResultsController, opérations par lots,NSCompoundPredicate - Votre app nécessite le partage de données CloudKit entre différents utilisateurs
- Vous maintenez un projet existant de grande envergure avec Core Data
- Les performances sur de très gros volumes de données sont critiques
La coexistence est possible
Si vous migrez progressivement, sachez que SwiftData et Core Data peuvent coexister dans la même application, pointant vers le même fichier de base de données. La seule contrainte (et elle est de taille) : les deux schémas ne doivent pas diverger. Chaque modification apportée à l'un doit être reflétée dans l'autre.
Bonnes pratiques et pièges à éviter
Organisation du code
- Un fichier par modèle — gardez vos classes
@Modeldans des fichiers séparés pour une maintenance plus facile - Utilisez des structs Codable pour les types complexes imbriqués plutôt que de créer des relations pour chaque sous-objet
- Versionnez votre schéma dès le premier release — créez un
VersionedSchemainitial même si vous n'avez pas encore de migration
Performance
- Utilisez
fetchLimit— ne chargez pas tous les objets si vous n'en affichez que 20 - Préchargez les relations avec
relationshipKeyPathsForPrefetchingsi vous savez que vous en aurez besoin - Évitez les
@Querytrop larges — ils s'exécutent dès que la vue apparaît et peuvent bloquer l'UI sur de gros datasets
Pièges courants
- Previews qui crashent — si vous créez une instance d'un modèle SwiftData sans
ModelContainerautour, votre preview crashera. C'est le piège classique. Utilisez toujours un container en mémoire dans vos previews. - Thread safety — les
ModelContainerpeuvent être passés entre threads, mais lesModelContextdoivent absolument rester sur le thread qui les a créés - Identifiants temporaires — après un
insert(), l'objet reçoit un ID temporaire. Ne stockez pas cet ID ; ré-interrogez aprèssave()si vous en avez besoin
Exemple complet : application de gestion de tâches
Pour conclure, voici un exemple fonctionnel qui combine tout ce qu'on a vu — modèles avec relations, CRUD, filtres dynamiques. Vous pouvez le copier-coller dans un projet Xcode et l'exécuter directement :
import SwiftUI
import SwiftData
// MARK: - Modèles
@Model
class Projet {
var nom: String
var couleur: String
@Relationship(deleteRule: .cascade, inverse: \Tache.projet)
var taches: [Tache]
init(nom: String, couleur: String = "blue") {
self.nom = nom
self.couleur = couleur
self.taches = []
}
}
@Model
class Tache {
var titre: String
var estTerminee: Bool
var dateCreation: Date
var projet: Projet?
init(titre: String, projet: Projet? = nil) {
self.titre = titre
self.estTerminee = false
self.dateCreation = .now
self.projet = projet
}
}
// MARK: - Vue principale
struct ProjetDetailView: View {
let projet: Projet
@Environment(\.modelContext) private var context
@State private var nouveauTitre = ""
var body: some View {
List {
Section("Nouvelle tâche") {
HStack {
TextField("Titre", text: $nouveauTitre)
Button("Ajouter") {
let tache = Tache(titre: nouveauTitre, projet: projet)
context.insert(tache)
nouveauTitre = ""
}
.disabled(nouveauTitre.isEmpty)
}
}
Section("Tâches (\(projet.taches.count))") {
ForEach(projet.taches.sorted(by: { $0.dateCreation > $1.dateCreation })) { tache in
HStack {
Button {
tache.estTerminee.toggle()
} label: {
Image(systemName: tache.estTerminee
? "checkmark.circle.fill"
: "circle")
.foregroundStyle(tache.estTerminee ? .green : .gray)
}
Text(tache.titre)
.strikethrough(tache.estTerminee)
}
}
.onDelete { indexSet in
let sorted = projet.taches.sorted(by: { $0.dateCreation > $1.dateCreation })
for index in indexSet {
context.delete(sorted[index])
}
}
}
}
.navigationTitle(projet.nom)
}
}
// MARK: - App
@main
struct GestionTachesApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
.modelContainer(for: [Projet.self, Tache.self])
}
}
FAQ — Questions fréquentes sur SwiftData
SwiftData peut-il être utilisé avec Objective-C ?
Non. SwiftData repose exclusivement sur des fonctionnalités avancées de Swift (macros, types natifs, protocoles modernes) qui n'ont pas d'équivalent en Objective-C. Si votre projet contient du code Objective-C, vous pouvez utiliser SwiftData uniquement dans les parties Swift.
Peut-on utiliser SwiftData sans SwiftUI ?
Oui, mais avec des limitations. La macro @Query ne fonctionne que dans les vues SwiftUI, mais vous pouvez utiliser FetchDescriptor et ModelContext directement dans n'importe quel code Swift — y compris UIKit. Par contre, vous perdez la réactivité automatique des vues et devrez gérer les mises à jour manuellement.
Quelle est la version minimale d'iOS requise pour SwiftData ?
SwiftData nécessite iOS 17 (ou macOS 14, watchOS 10, tvOS 17) au minimum. L'héritage de modèles nécessite iOS 26. Si vous devez supporter des versions antérieures, Core Data reste votre seule option pour la persistance native Apple.
Comment gérer les previews Xcode avec SwiftData ?
Les previews crashent si aucun ModelContainer n'est disponible. La solution classique est d'utiliser un container en mémoire :
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Tache.self, configurations: config)
return ListeTaches()
.modelContainer(container)
}
SwiftData est-il adapté aux applications en production ?
Oui, à condition de cibler iOS 17+. SwiftData est utilisé en production par de nombreuses applications depuis 2024. Cela dit, gardez en tête ses limitations actuelles : pas de synchronisation CloudKit partagée, pas de NSFetchedResultsController, et des performances légèrement inférieures à Core Data sur de très gros volumes. Pour la majorité des apps de complexité simple à modérée, SwiftData est tout à fait prêt pour la production.