Objectif : Nous allons ajouter manuellement tous les éléments nécessaires à l’utilisation de Core Data.
Comme nous n’avons pas coché l’option Core Data, Xcode n’a pas créé de fichier .xcdatamodeld par défaut. Nous allons le créer nous-mêmes.
Votre modèle de données contient désormais une entité Product avec deux attributs : name et quantity.
Maintenant, nous allons créer un fichier Swift pour configurer la stack Core Data
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init() {
// « Products » correspond au nom du fichier .xcdatamodeld (sans l’extension)
container = NSPersistentContainer(name: "Products")
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
// En cas d’erreur, on arrête l’application (fatalError)
// Pour une app de production, on gérerait l’erreur différemment
fatalError("Échec du chargement du store: \(error)")
}
}
}
}
Dans votre fichier CoreDataDemoApp.swift, on va utiliser le PersistenceController pour fournir le viewContext aux vues SwiftUI.
import SwiftUI
@main
struct CoreDataDemoApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
// On injecte le viewContext dans l’environnement
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
Avantage : Toutes les vues pourront accéder à @Environment(\.managedObjectContext) pour créer, lire ou supprimer des objets.
import SwiftUI
import CoreData
struct ContentView: View {
// Champs de saisie pour le nom et la quantité
@State private var name = ""
@State private var quantity = ""
// Récupération du contexte Core Data via l’environnement
@Environment(\.managedObjectContext) private var viewContext
// FetchRequest pour récupérer tous les Product
@FetchRequest(
entity: Product.entity(),
sortDescriptors: [] // On pourra ajouter un tri plus tard
)
private var products: FetchedResults< Product >
var body: some View {
NavigationStack {
VStack {
TextField("Nom du produit", text: $name)
TextField("Quantité", text: $quantity)
.keyboardType(.numberPad)
HStack {
Spacer()
Button("Add") {
addProduct()
}
Spacer()
Button("Clear") {
name = ""
quantity = ""
}
Spacer()
}
.padding()
// Affichage de la liste des produits
List {
ForEach(products) { product in
HStack {
Text(product.name ?? "Inconnu")
Spacer()
Text(product.quantity ?? "0")
}
}
.onDelete(perform: deleteProducts)
}
.listStyle(.plain)
}
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.navigationTitle("Inventaire")
}
}
}
Toujours dans ContentView :
extension ContentView {
private func addProduct() {
withAnimation {
let newProduct = Product(context: viewContext)
newProduct.name = name
newProduct.quantity = quantity
saveContext()
}
}
private func deleteProducts(offsets: IndexSet) {
withAnimation {
offsets.map { products[$0] }.forEach(viewContext.delete)
saveContext()
}
}
private func saveContext() {
do {
try viewContext.save()
} catch {
// Arrêt brutal si erreur (fatalError)
// En production, on afficherait un message d’erreur plus propre
fatalError("Erreur lors de la sauvegarde : \(error)")
}
}
}
Pour trier par ordre alphabétique, on peut ajouter un NSSortDescriptor :
@FetchRequest(
entity: Product.entity(),
sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]
)
private var products: FetchedResults< Product >
On peut créer une vue de recherche (ResultsView) qui utilise un NSPredicate :
struct ResultsView: View {
var name: String
var viewContext: NSManagedObjectContext
@State var matches: [Product]?
var body: some View {
VStack {
List {
ForEach(matches ?? []) { match in
HStack {
Text(match.name ?? "Not found")
Spacer()
Text(match.quantity ?? "Not found")
}
}
}
.navigationTitle("Results")
}
.task {
let fetchRequest: NSFetchRequest = Product.fetchRequest()
fetchRequest.entity = Product.entity()
// CONTAINS[cd] = insensible à la casse et aux accents
fetchRequest.predicate = NSPredicate(format: "name CONTAINS[cd] %@", name)
matches = try? viewContext.fetch(fetchRequest)
}
}
}
Cela permet d’afficher uniquement les produits selon le nom.
On peut ajouter des boutons pour accéder à la vue de recherche :
...
HStack {
Spacer()
Button("Add") {
addProduct()
}
Spacer()
NavigationLink(destination: ResultsView(name: name, viewContext: viewContext)) {
Text("Find")
}
Spacer()
Button("Clear") {
name = ""
quantity = ""
}
Spacer()
}
...
Lorsqu’un utilisateur sélectionne un produit dans la liste, il doit pouvoir modifier ses informations (nom et quantité) et sauvegarder ces modifications. Nous allons créer une vue EditProductView qui affichera un formulaire pré-rempli avec les données existantes du produit.
struct EditProductView: View {
@ObservedObject var product: Product
var viewContext: NSManagedObjectContext
@State private var name: String = ""
@State private var quantity: String = ""
@State private var showConfirmation = false
var body: some View {
Form {
Section(header: Text("Informations du produit")) {
TextField("Nom du produit", text: $name)
TextField("Quantité", text: $quantity)
.keyboardType(.numberPad)
}
Section {
Button("Enregistrer") {
saveChanges()
}
.buttonStyle(.borderedProminent)
.disabled(name.isEmpty || quantity.isEmpty) // Empêche l'enregistrement si les champs sont vides
}
}
.navigationTitle("Modifier Produit")
.onAppear {
loadData()
}
.alert("Modification enregistrée", isPresented: $showConfirmation) {
Button("OK", role: .cancel) { }
}
}
/// Charge les données actuelles du produit dans les champs de saisie.
private func loadData() {
name = product.name ?? ""
quantity = product.quantity ?? ""
}
/// Sauvegarde les modifications dans Core Data.
private func saveChanges() {
product.name = name
product.quantity = quantity
do {
try viewContext.save()
showConfirmation = true
} catch {
print("Erreur lors de la sauvegarde : \(error.localizedDescription)")
}
}
}
Nous devons maintenant permettre à l’utilisateur d’accéder à EditProductView depuis la liste des produits affichée dans ContentView. Pour cela, nous allons utiliser NavigationLink.
...
NavigationView {
List {
ForEach(products) { product in
NavigationLink(destination: EditProductView(product: product, viewContext: viewContext)) {
HStack {
Text(product.name ?? "Inconnu")
.font(.headline)
Spacer()
Text(product.quantity ?? "0")
.foregroundColor(.secondary)
}
}
}
.onDelete(perform: deleteProducts)
}
.navigationTitle("Produits")
}
...
Au lieu d’utiliser fatalError, on peut afficher une alerte, ou logger l’erreur dans la console, etc.