SwiftData sur iCloud

Translated by: gpt-4o-2024-08-06 | Original version

Remarque : Le dépôt GitHub associé à cet article se trouve ici.

En apprenant Swift et SwiftUI et en commençant à les utiliser, j’ai souvent rencontré des difficultés avec CloudKit. Trop souvent… Voici une configuration minimale que j’ai utilisée pour me rappeler comment j’ai mis en place les choses et documenter mes apprentissages.

Point de départ

Presque tout ce que je sais sur SwiftUI vient du formidable tutoriel 100 Days of SwiftUI de Paul Hudson. Il fait un travail fantastique pour expliquer SwiftUI et offre ce cours bien entretenu gratuitement.

Pour ne pas avoir à tout recommencer de zéro, j’utilise la leçon du cours qui introduit SwiftData : Au Jour 53, Paul commence un nouveau projet appelé Bookworm qui utilise SwiftData pour stocker ses données de livres. À la fin du Jour 55, il a construit une petite application sympathique qui stocke ses données dans SwiftData. Si vous n’êtes pas à l’aise avec SwiftData ou ne vous en souvenez pas, je recommande fortement de regarder les vidéos de ces 3 jours. Son application utilise SwiftData, mais pas dans le cloud, uniquement localement sur l’appareil.

Je vais utiliser cela comme point de départ pour y ajouter la synchronisation iCloud. Vous pouvez trouver l’application Bookworm dans le projet Github de Paul Hudson : Elle se trouve sous SwiftUI > project 11.

Si vous clonez/copiez le répertoire project 11 et l’ouvrez avec Xcode, vous devez définir votre Équipe et Identifiant de Bundle pour l’exécuter.

Nous allons maintenant essayer d’y ajouter la synchronisation iCloud.

Étape par étape

Ajouter des capacités au projet Xcode

Pour utiliser CloudKit pour la synchronisation, nous devons ajouter 2 capacités :

  • CloudKit (évidemment)
  • Notification : Cela est nécessaire pour que l’appareil puisse recevoir les signaux indiquant que les données ont changé et doivent être resynchronisées avec le cloud.

Si vous ouvrez Xcode > Projet : MiniSwiftData > Cible : MiniSwiftData > Signature & Capacités, voici ce que vous devriez voir :

texte alternatif

Cliquez sur le bouton + Capability en haut à gauche et recherchez iCloud :

texte alternatif

Ensuite, dans la section iCloud, sélectionnez CloudKit comme Service :

texte alternatif

Ensuite, en dessous, nous avons besoin d’un conteneur. Cliquez sur + et entrez un nom. Le nom habituel serait l’identifiant de bundle, dans mon cas c’est com.grtnr.Bookworm (je le copie-colle simplement depuis le champ Identifiant de Bundle).

texte alternatif

Cela mène à ceci :

texte alternatif

C’est en rouge parce qu’il n’a pas encore été créé, cela prend simplement un peu de temps.

Pour garder les appareils synchronisés, iCloud utilise la Notification Push à Distance, donc nous devons également ajouter ce service. Cliquez à nouveau sur + Capability et recherchez Back… :

texte alternatif

Ensuite, dans la liste des services de la section Modes en Arrière-plan, sélectionnez Notifications à distance :

texte alternatif

Remarque : Probablement à ce stade, le nom du conteneur est passé de rouge à noir car il a été créé dans iCloud.

Rendre le modèle prêt pour CloudKit

Lorsque vous démarrez l’application maintenant, vous obtenez une erreur similaire à celle-ci :

CoreData: error: Store failed to load.  <NSPersistentStoreDescription: 0x600000c0e670> (type: SQLite, url: file:///Users/tgartner/Library/Developer/CoreSimulator/Devices/1464DFC4-EE76-43DB-B178-C33F1FA97A91/data/Containers/Data/Application/34DEE59B-975D-4784-8A87-DC38A9D9DA37/Library/Application%20Support/default.store) with error = Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=CloudKit integration requires that all attributes be optional, or have a default value set. The following attributes are marked non-optional but do not have a default value:
Book: author
Book: genre
Book: rating
Book: review
Book: title} with userInfo {
    NSLocalizedFailureReason = "CloudKit integration requires that all attributes be optional, or have a default value set. The following attributes are marked non-optional but do not have a default value:\nBook: author\nBook: genre\nBook: rating\nBook: review\nBook: title";
}

La raison est qu’un modèle doit respecter quelques restrictions. Lors de la création de modèles pour les utiliser avec CloudKit, nous devons respecter certaines règles :

  • Valeurs par défaut des propriétés : Chaque propriété doit avoir une valeur par défaut - sauf si elle est optionnelle.
  • Ne peut pas utiliser @Unique

J’ai donc simplement modifié le fichier Book.swift :

import Foundation
import SwiftData

@Model
class Book {
    var title: String = ""
    var author: String = ""
    var genre: String = ""
    var review: String = ""
    var rating: Int = 3

    init(title: String, author: String, genre: String, review: String, rating: Int) {
        self.title = title
        self.author = author
        self.genre = genre
        self.review = review
        self.rating = rating
    }
}

Maintenant, j’exécute l’application sur un simulateur, j’entre 3 livres, je l’exécute sur mon véritable iPhone - et voilà, les livres apparaissent sur mon iPhone !! 🥰

Investigation, Tests, Expérimentations, peut-être compréhension…

Console CloudKit

Lors de l’investigation de ce qui se passe, il y a un outil très utile : La Console CloudKit :

texte alternatif

Sélectionnez CloudKit Database puis sélectionnez la base de données Bookworm :

texte alternatif

Interroger les données

Pour voir quels livres ont été créés et enregistrés, vous devez sélectionner les options et filtres appropriés

texte alternatif

  • Assurez-vous qu’au niveau supérieur, vous avez sélectionné la base de données Bookworm > Développement
  • Sélectionnez Base de données privée : Cela signifie que nous avons une séparation des données par utilisateur
  • Dans le menu déroulant de la zone, sélectionnez celle qui n’est pas _default, dans mon cas, c’est com.apple.coredata.cloudkit.zone. Je n’ai aucune idée (pour l’instant) de ce que ces zones signifient…
  • Dans TYPE D’ENREGISTREMENT, sélectionnez CD_Book, car nous voulons examiner nos livres

Appuyez sur Query Records et obtenez une erreur… 😂 texte alternatif

Pour résoudre cela, sélectionnez dans le menu de gauche : Schéma > Types d’enregistrements et voyez ceci :

texte alternatif

Nous voyons qu’il avait raison avec son message d’erreur : recordName n’est pas marqué comme interrogeable. Alors, corrigeons cela.

Dans le menu de gauche, sélectionnez Schéma > Indexes et cliquez sur + pour créer un nouvel index comme ceci :

texte alternatif

Retournez à Données > Enregistrements, définissez vos filtres et commutateurs : Base de données privée, zone com.apple.coredata.cloudkit.zone, TYPE D’ENREGISTREMENT sur CD_Books et appuyez sur Query Records et Baammm :

texte alternatif

Synchronisation en temps réel

L’une des fonctionnalités très cool de CloudKit est la synchronisation quasi en temps réel : Vous modifiez les données sur un appareil, et les modifications deviennent visibles sur l’autre appareil très rapidement - bien sûr sur des appareils connectés avec le même AppleId.

Le fonctionnement est qu’une notification est envoyée à tous les appareils connectés avec cet AppleId, puis ils commencent une synchronisation.

Malheureusement, les simulateurs ne reçoivent pas ces notifications. Donc, pour le tester, vous devez effectuer des modifications sur un simulateur, puis les voir apparaître sur votre appareil réel. Bien sûr, cela fonctionne également si vous utilisez 2 appareils physiques.

Lecture

Quelques documents et explications précieux que j’ai trouvés.

Apple

Si le portail montre que l’association entre votre conteneur CloudKit et l’ID de l’application est correcte, mais que l’erreur persiste, il est probable que l’association ne soit pas synchronisée avec le serveur CloudKit. Dans ce cas, envisagez d’utiliser un nouveau conteneur CloudKit pour poursuivre votre développement.

Erreurs fréquentes

Valeurs par défaut pour les champs

Les champs d’un modèle doivent avoir des valeurs standard pour pouvoir être enregistrés dans CloudKit. Vous n’en avez pas besoin pour SwiftData si vous enregistrez simplement les données localement sur l’appareil.

Non connecté avec AppleId

Si l’appareil n’est pas connecté avec un AppleId, il ne synchronise pas.

Les droits ont été modifiés pendant la construction

Entitlements file "MiniSwiftData.entitlements" was modified during the build, which is not supported. You can disable this error by setting 'CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION' to 'YES', however this may cause the built product's code signature or provisioning profile to contain incorrect entitlements.

Bien sûr, je n’ai pas modifié les droits pendant que Xcode construisait. Mais la façon de résoudre cela est assez simple :

  • Dans Xcode, allez à Projet > Cible > Signature & Capacités
  • Décochez (désélectionnez) Gérer automatiquement la signature
  • Re-sélectionnez-le
  • Sélectionnez votre Équipe

…et voilà : Xcode reconstruit maintenant votre fichier de droits.

Impossible d’initialiser sans un compte iCloud (CKAccountStatusNoAccount).

Si vous n’êtes pas connecté sur votre appareil ou simulateur, la synchronisation iCloud ne peut pas fonctionner.

J’ai vu cela et j’étais surpris, car j’ÉTAIS connecté à iCloud. MAIS l’accès à iCloud était désactivé pour l’application :

Paramètres > iCloud > Enregistré sur iCloud | Tout voir. Voici à quoi ressemblait le coupable :

texte alternatif