SwiftData auf iCloud
Hinweis: Das GitHub-Repo, das zu diesem Artikel gehört, findest du hier.
Während ich Swift & SwiftUI gelernt habe und es zu verwenden begann, hatte ich oft Schwierigkeiten, CloudKit zu nutzen. Zu oft… Dies ist eine minimale Einrichtung, die ich verwendet habe, um mich in Zukunft daran zu erinnern, wie ich die Dinge eingerichtet habe und um meine Erkenntnisse zu dokumentieren.
- Ausgangspunkt
- Schritt für Schritt
- Fähigkeiten zum Xcode-Projekt hinzufügen
- Modell CloudKit-fähig machen
- Untersuchen, Testen, Herumspielen, vielleicht Verstehen…
- CloudKit-Konsole
- In Echtzeit synchronisieren
- Lesen
- Apple
- Häufige Fehler
- Standardwerte für Felder
- Nicht mit AppleId angemeldet
- Entitlements wurden während des Builds geändert
- Kann ohne iCloud-Konto nicht initialisieren (CKAccountStatusNoAccount).
Ausgangspunkt
Fast alles, was ich über SwiftUI weiß, stammt aus dem großartigen Tutorial 100 Days of SwiftUI von Paul Hudson. Er leistet fantastische Arbeit bei der Erklärung von SwiftUI und bietet diesen gut gepflegten Kurs kostenlos an.
Um nicht komplett von vorne anfangen zu müssen, nutze ich die Lektion aus dem Kurs, die SwiftData einführt: An Tag 53 startet Paul ein neues Projekt namens Bookworm, das SwiftData verwendet, um seine Buchdaten zu speichern. Am Ende von Tag 55 hat er eine niedliche kleine Anwendung gebaut, die ihre Daten in SwiftData speichert. Falls du nicht fließend in SwiftData bist oder dich nicht daran erinnerst, empfehle ich dringend, die Videos dieser 3 Tage anzusehen. Seine App verwendet SwiftData, aber nicht in der Cloud, sondern nur lokal auf dem Gerät.
Ich werde dies als Ausgangspunkt nutzen, um iCloud-Synchronisation hinzuzufügen. Du findest die Bookworm-App in Paul Hudsons GitHub-Projekt: Sie befindet sich unter SwiftUI > project 11.
Wenn du das Verzeichnis project 11 klonst / kopierst und mit Xcode öffnest, musst du dein Team & Bundle Identifier einstellen, um es auszuführen.
Jetzt werden wir versuchen, iCloud-Synchronisation hinzuzufügen.
Schritt für Schritt
Fähigkeiten zum Xcode-Projekt hinzufügen
Um CloudKit für die Synchronisation zu verwenden, müssen wir 2 Fähigkeiten hinzufügen:
- CloudKit (klar)
- Benachrichtigung: Dies ist notwendig, damit das Gerät die Signale erhält, dass sich Daten geändert haben und erneut mit der Cloud synchronisiert werden müssen.
Wenn du Xcode > Project: MiniSwiftData > Target: MiniSwiftData > Signing & Capabilities öffnest, solltest du Folgendes sehen:

Klicke auf die Schaltfläche + Capability oben links und suche nach iCloud:

Wähle dann im Abschnitt iCloud CloudKit als Dienst aus:

Unten brauchen wir dann einen Container. Klicke auf + und gib einen Namen ein. Der übliche Name wäre der Bundle Identifier, in meinem Fall ist das com.grtnr.Bookworm (ich kopiere & füge ihn einfach aus dem Bundle Identifier-Feld ein).

Das führt zu diesem Ergebnis:

Es ist rot, weil es noch nicht erstellt wurde, das dauert einfach eine Weile.
Um Geräte synchron zu halten, verwendet iCloud die Remote Push Notification, daher müssen wir diesen Dienst ebenfalls hinzufügen. Klicke erneut auf + Capability und suche nach Back…:

Wähle dann in der Liste der Dienste im Abschnitt Background Modes die Option Remote notifications:

Hinweis: Wahrscheinlich ist der Containername inzwischen von rot zu schwarz gewechselt, da er in iCloud erstellt wurde.
Modell CloudKit-fähig machen
Wenn wir die App jetzt starten, erhalten wir einen Fehler ähnlich diesem:
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";
}
Der Grund ist, dass ein Modell ein paar Einschränkungen erfüllen muss. Beim Erstellen von Modellen für die Verwendung mit CloudKit müssen wir einige Regeln beachten:
- Standardwerte für Eigenschaften: Jede Eigenschaft benötigt einen Standardwert - es sei denn, sie ist optional.
@Uniquekann nicht verwendet werden
Also habe ich einfach die Datei Book.swift geändert:
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
}
}
Jetzt starte ich die App auf einem Simulator, gebe 3 Bücher ein, starte sie auf meinem echten iPhone - et voilà, die Bücher erscheinen auf meinem iPhone!! 🥰
Untersuchen, Testen, Herumspielen, vielleicht Verstehen…
CloudKit-Konsole
Beim Untersuchen, was passiert, gibt es ein sehr hilfreiches Tool: Die CloudKit-Konsole:

Wähle CloudKit Database und dann die Bookworm-Datenbank:

Daten abfragen
Um zu sehen, welche Bücher erstellt und gespeichert wurden, musst du die richtigen Optionen und Filter auswählen

- Stelle sicher, dass du auf der obersten Ebene die Bookworm-Datenbank > Entwicklung ausgewählt hast
- Wähle Private Datenbank: Das bedeutet, dass wir eine pro Benutzer Trennung der Daten haben
- Im Zonen-Dropdown wähle die, die nicht die _default ist, in meinem Fall ist das com.apple.coredata.cloudkit.zone. Ich habe keine Ahnung (noch nicht), worum es bei diesen Zonen geht…
- In RECORD TYPE wähle CD_Book, weil wir unsere Bücher ansehen wollen
Drücke Query Records und erhalte einen Fehler… 😂

Um dies zu beheben, wähle im Menü auf der linken Seite: Schema > Record Types und sieh dir das an:

Wir sehen, dass er mit seiner Fehlermeldung Recht hatte: recordName ist nicht als abfragbar markiert. Also lass uns das beheben.
Wähle im linken Menü Schema > Indexes und klicke auf +, um einen neuen Index wie folgt zu erstellen:

Kehre zu den Daten > Records zurück, setze deine Filter & Schalter: Private Datenbank, Zone com.apple.coredata.cloudkit.zone, RECORD TYPE zu CD_Books und drücke Query Records und Baammm:

In Echtzeit synchronisieren
Eine der sehr coolen Funktionen von CloudKit ist die nahezu Echtzeit-Synchronisation: Du änderst Daten auf einem Gerät, und die Änderungen werden sehr schnell auf dem anderen Gerät sichtbar - natürlich auf Geräten, die mit derselben AppleId angemeldet sind.
So funktioniert das: Eine Benachrichtigung wird an alle Geräte gesendet, die mit dieser AppleId angemeldet sind, und dann starten sie eine Synchronisation.
Leider erhalten Simulatoren diese Benachrichtigungen nicht. Um es zu testen, musst du also Änderungen auf einem Simulator vornehmen und dann sehen, wie sie auf deinem echten Gerät erscheinen. Natürlich funktioniert es auch, wenn du 2 physische Geräte verwendest.
Lesen
Einige wertvolle Dokumente und Erklärungen, die ich gefunden habe.
Apple
- Enabling CloudKit in Your App: Erklärt, wie du deine App konfigurierst, um Daten in iCloud mit CloudKit zu speichern.
-
Managing iCloud Containers with CloudKit Database App: Erklärt, wie du Daten in deinen Containern über das Apple Web Dev untersuchen und sehen kannst.
-
TN3164: Debugging the synchronization of NSPersistentCloudKitContainer: Während ich versuchte herauszufinden, warum meine App nicht mit ihrem Container verbinden konnte und ständig Zugriffsprobleme meldete, war dies der Schlüsselsatz aus dieser TechNote, der mich rettete:
Wenn das Portal zeigt, dass die Zuordnung zwischen deinem CloudKit-Container und der App-ID korrekt ist, der Fehler jedoch weiterhin besteht, liegt es höchstwahrscheinlich daran, dass die Zuordnung nicht mit dem CloudKit-Server synchronisiert ist. In diesem Fall solltest du in Betracht ziehen, einen neuen CloudKit-Container für die weitere Entwicklung zu verwenden.
Häufige Fehler
Standardwerte für Felder
Die Felder eines Modells müssen Standardwerte haben, damit sie in CloudKit gespeichert werden können. Du benötigst sie nicht für SwiftData, wenn du die Daten nur lokal auf dem Gerät speicherst.
Nicht mit AppleId angemeldet
Wenn das Gerät nicht mit einer AppleId angemeldet ist, synchronisiert es nicht.
Entitlements wurden während des Builds geändert
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.
Natürlich habe ich die Entitlements nicht direkt während des Xcode-Builds geändert. Aber die Lösung dafür ist ziemlich einfach:
- Gehe in Xcode zu Project > Target > Signing & Capabilities
- Deaktiviere (deselektiere) Automatically manage signing
- Wähle es erneut aus
- Wähle dein Team
…das war’s: Xcode erstellt nun deine Entitlement-Datei neu.
Kann ohne iCloud-Konto nicht initialisieren (CKAccountStatusNoAccount).
Wenn du nicht auf deinem Gerät oder Simulator angemeldet bist, kann die iCloud-Synchronisation nicht funktionieren.
Ich habe dies gesehen und war überrascht, weil ich bei iCloud angemeldet WAR. ABER der iCloud-Zugriff war für die App deaktiviert:
Einstellungen > iCloud > In iCloud gespeichert | Alle anzeigen. So sah der fehlerhafte Typ aus:
