Usually, when you call fetchRecordZoneChanges with the previous change token, you get a list of the record ID’s that have been deleted since your last fetch.
But if you get a changeTokenExpired error because it‘s been too long since you last fetched, you have to call fetch again without a token.
For my specific application, I still need to know, though, if any records have been deleted since my last sync. How can I get that information if I no longer have a valid change token?
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have implemented CKSyncEngine synchronization, and it works well. I can update data on one device and see the changes propagate to another device quickly. However, the initial sync when a user downloads the app on a new device is a significant issue for both me and my users.
One problem is that the sync engine fetches deletion events from the server. On a new device, the local database is empty, so these deletions are essentially no-ops. This would not be a big problem if there were only a few records or if it was fast. I measured the initial sync and found that there are 150 modified records and 62,168 deletions. Counting these alone takes over five minutes, even without processing them. The deletions do nothing because the local database has nothing to delete, yet they still add a significant delay.
I understand that the sync engine ensures consistency across all devices, but five minutes of waiting with the app open just to insert a small number of records is excessive. The problem would be worse if there were tens of thousands of new records to insert, since downloading and saving the data would take even longer.
This leads to a poor user experience. Users open the app and see data being populated for several minutes, or they are stuck on a screen that says the data is being synchronized with iCloud.
I am wondering if there is a way to make the sync engine ignore deletion events when the state serialization is nil. Alternatively, is there a recommended method for handling initial synchronization more efficiently?
One idea I considered is storing all the data as a backup in iCloud Documents, along with the state serialization at that point in time. When a user opens the app for the first time, I could download the file, extract the data, and set the state serialization to the saved value. I am not sure if this would work. I do not know if state serialization is tied to the device or if it only represents the point where the sync engine left off. My guess is that it might reference some local device storage.
I am not sure what else to try. I could fetch all data using CloudKit, create the sync engine with an empty state serialization, and let it fetch everything again, but that would still take a long time.
My records are very small, mostly a date when something happened and an ID referencing the parent. Since the app tracks watched episodes, I only store the date the user watched the episode and the ID of that episode.
I'm using SwiftData with CloudKit private database. I was able to identify the error on my device by debugging in Xcode with com.apple.CoreData.SQLDebug flag. However, in Production, I couldn't find a way to get the cause of errors.
I tried inspecting the error coming from eventChangedNotification. The NSPersistentCloudKitContainer.Event error does not contain any underlying error (neither CKError.userInfo nor in NSError.underlyingError). It only reports a partial failure with CKErrorDomain code 2.
If a user encounter an error, there seems to be no way to retrieve the error details.
Is there any way to access the error details or logs in Production?
Hello,
I have 3 model versions and I'm trying to step through migration.
Version 2 makes significant changes to v1. As a result, I've renamed the entities in question by appending _v2 to their name, as the data isn't important to retain.
v3, remove's the appended version number from v2.
Setting the .xcdatamodeld to v3 and the migrations steps array as follows causes the app to error
[
NSLightweightMigrationStage([v1]),
NSLightweightMigrationStage([v2]),
NSLightweightMigrationStage([v3]),
]
CoreData: error: <NSPersistentStoreCoordinator: 0x10740d680>: Attempting recovery from error encountered during addPersistentStore: 0x10770f8a0 Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration."
An error occurred during persistent store migration. Cannot merge multiple root entity source tables into one destination entity root table.
I find this odd because if I run the migration independently across app launches, the migration appears to drop the no longer used tables in v2, then re-add them back in v3. So it seems to me that something is not finishing completely with the fully stepped through migration.
--
I'm also unable to understand how to use NSCustomMigrationStage I've tried setting it to migrate from v1, to v2, but I'm getting a crash with error
Duplicate version checksums across stages detected
I've spent a few months writing an app that uses SwiftData with inheritance. Everything worked well until I tried adding CloudKit support. To do so, I had to make all relationships optional, which exposed what appears to be a bug. Note that this isn't a CloudKit issue -- it happens even when CloudKit is disabled -- but it's due to the requirement for optional relationships.
In the code below, I get the following error on the second call to modelContext.save() when the button is clicked:
Could not cast value of type 'SwiftData.PersistentIdentifier' (0x1ef510b68) to 'SimplePersistenceIdentifierTest.Computer' (0x1025884e0).
I was surprised to find zero hit when Googling "Could not cast value of type 'SwiftData.PersistentIdentifier'".
Some things to note:
Calling teacher.computers?.append(computer) instead of computer.teacher = teacher results in the same error.
It only happens when Teacher inherits Person.
It only happens if modelContext.save() is called both times.
It works if the first modelContext.save() is commented out.
If the second modelContext.save()is commented out, the error occurs the second time the model context is saved (whether explicitly or implicitly).
Keep in mind this is a super simple repro written to generate on demand the error I'm seeing in a normal app. In my app, modelContext.save() must be called in some places to update the UI immediately, sometimes resulting in the error seconds later when the model context is saved automatically. Not calling modelContext.save() doesn't appear to be an option.
To be sure, I'm new to this ecosystem so I'd be thrilled if I've missed something obvious! Any thoughts are appreciated.
import Foundation
import SwiftData
import SwiftUI
struct ContentView: View {
@Environment(\.modelContext) var modelContext
var body: some View {
VStack {
Button("Do it") {
let teacher = Teacher()
let computer = Computer()
modelContext.insert(teacher)
modelContext.insert(computer)
try! modelContext.save()
computer.teacher = teacher
try! modelContext.save()
}
}
}
}
@Model
class Computer {
@Relationship(deleteRule: .nullify)
var teacher: Teacher?
init() {}
}
@Model
class Person {
init() {}
}
@available(iOS 26.0, macOS 26.0, *)
@Model
class Teacher: Person {
@Relationship(deleteRule: .nullify, inverse: \Computer.teacher)
public var computers: [Computer]? = []
override init() {
super.init()
}
}
I have a total of 100 asset packs associated with my app but I have archived 5 of them. Unfortunately I am now unable to upload any more asset packs (the reported error is "backgroundAsset limit reached -- This app has already reached the maximum number of active backgroundAssets. Maximum allowed is 100.") I assumed that archiving asset packs would make them inactive (and thus not count against the limit). This seems to not be the case and I'm not sure how I can upload new asset packs.
There is a conflict in SwiftData (specifically when synced with CloudKit) when a @Model attribute shares the same name as a case within its assigned enum type. When this occurs, accessing the attribute on a model instance consistently returns the value corresponding to the enum case name, rather than the actual value persisted in the database.
Steps to Reproduce
Define an enumeration (e.g., Status) with a case that matches a planned property name (e.g., case status).
Create a SwiftData @Model that uses this enum.
Name the property in the model the same as the enum case.
Attempt to save and then retrieve the value.
Example Code
enum TaskStatus: String, Codable {
case status // The conflict source
case pending
case completed
}
@Model
class TodoItem {
// Conflict: Property name matches enum case name
var status: TaskStatus
init(status: TaskStatus) {
self.status = status
}
}
Expected Behavior
The property item.status should return the value stored in the database (e.g., .pending or .completed).
Actual Behavior
The property item.status consistently resolves to the enum case .status, ignoring the actual persisted data.
Topic:
App & System Services
SubTopic:
iCloud & Data
My iOS app uses CloudKit key-value storage. I have not updated the app in a few years but it works fine. Since it was last updated, I transferred the app from an old organization to my personal developer account. Now that I'm working on the app again I get an error: Provisioning profile "iOS Team Provisioning Profile: com.company.app" doesn't match the entitlements file's value for the com.apple.developer.ubiquity-kvstore-identifier entitlement.
In the entitlement file, it has $(TeamIdentifierPrefix)$(CFBundleIdentifier) as the value for iCloud Key-Value Store. I've verified the variables resolve as expected. When I parse the provisioning profile there is no entitlement value for key-value storage. What am I getting wrong?
Hello,
I have an asset pack that I'm use to periodically distribute a sqlite database thats being used to an NSPersistentStore.
Because the database is over a few GBs, and the files in an AssetPack are not mutable, I have to stream the database into a temporary file, then replace my NSPersistentStore.
This requires that the user has 3x the storage available of the database, and permanently uses twice to storage needed.
I'd like:
To be able to mark a URL/File to be accessible for read/write access
To be able to mark a file / URL as consumed when it's no needed. So that it can be cleared from the user storage while still maintaining an active subscription to the asset pack for updates.
Thank you
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
Files and Storage
On demand resources
Core Data
Background Assets
Hey everyone I just ran into an issue where I couldn't sync the model below fully by using CloudKit,
enum LinkMapV3_1: VersionedSchema {
static let versionIdentifier: Schema.Version = .init(3, 1, 0)
static var models: [any PersistentModel.Type] {
[AnnotationData.self, GroupData.self, Item.self, Deployment.self, History.self]
}
// MARK: - Data
@Model
class AnnotationData {
var name: String = ""
var longitude: Double = 0.0
var latitude: Double = 0.0
var order: Int = -1
var level: Int = 1
var detail: String = ""
@Relationship(deleteRule: .nullify, inverse: \GroupData.annotation)
var groups: [GroupData]?
@Relationship(deleteRule: .nullify, inverse: \AnnotationData.to)
var from: AnnotationData?
var to: AnnotationData?
var history: History?
}
// MARK: - History
@Model
class History {
var id: UUID = UUID()
var timestamp: Date = Date()
@Relationship(deleteRule: .nullify, inverse: \AnnotationData.history)
var annotations: [AnnotationData]?
@Relationship(deleteRule: .nullify, inverse: \GroupData.history)
var groups: [GroupData]?
@Relationship(deleteRule: .nullify, inverse: \Item.history)
var items: [Item]?
@Relationship(deleteRule: .nullify, inverse: \Deployment.history)
var deployment: Deployment?
var formattedDate: String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: timestamp)
}
var timeAgo: String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
return formatter.localizedString(for: timestamp, relativeTo: Date())
}
}
}
So when trying to sync with the code in documentation
let modelContainer: ModelContainer
init() {
let config = ModelConfiguration()
typealias vs = LinkMapV3_1
do {
#if DEBUG
// Use an autorelease pool to make sure Swift deallocates the persistent
// container before setting up the SwiftData stack.
try autoreleasepool {
let desc = NSPersistentStoreDescription(url: config.url)
let opts = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.name.Endsunset.LinkMap.SwiftData.v1")
desc.cloudKitContainerOptions = opts
// Load the store synchronously so it completes before initializing the
// CloudKit schema.
desc.shouldAddStoreAsynchronously = false
if let mom = NSManagedObjectModel.makeManagedObjectModel(for: [vs.AnnotationData.self, vs.GroupData.self, vs.Item.self, vs.Deployment.self, vs.History.self]) {
let container = NSPersistentCloudKitContainer(name: "LinkMap", managedObjectModel: mom)
container.persistentStoreDescriptions = [desc]
container.loadPersistentStores {_, err in
if let err {
fatalError(err.localizedDescription)
}
}
// Initialize the CloudKit schema after the store finishes loading.
try container.initializeCloudKitSchema()
// Remove and unload the store from the persistent container.
if let store = container.persistentStoreCoordinator.persistentStores.first {
try container.persistentStoreCoordinator.remove(store)
}
}
}
#endif
modelContainer = try ModelContainer(for:
vs.AnnotationData.self,
vs.GroupData.self,
vs.Item.self,
vs.Deployment.self,
vs.History.self,
configurations: config)
} catch {
fatalError(error.localizedDescription)
}
}
The output is
Console Output
Where you can see
Output Extract
Optional arrays with @Relationship are missing, and the entry of record types on cloudkit database container are also missing it.
When I attempt to insert an annotation, I got
SwiftData/PersistentModel.swift:559: Fatal error: This KeyPath does not appear to relate AnnotationData to anything - \AnnotationData.groups
It gets more suspicious when restart the app and try again, the above error end with "AnnotationData.history", and if I tried again the above error end with "AnnotationData.from"... and so on.
No matter how my app stop working.
I'm setting up App Entities for my SwiftData models and I'm not sure about the best way to reference SwiftData model properties in the AppEntity.
I have a SwiftData model with many properties:
@Model
final class Contact {
@Attribute(.unique) var id: UUID = UUID()
var name: String
var phoneNumber: String
var email: String
var website: URL?
var birthday: Date?
var notes: String
// ... many more properties
}
I want to expose these properties on my AppEntity so they're available for system features, such as giving Apple Intelligence more context about on-screen content.
struct ContactEntity: AppEntity {
var id: UUID
@Property(title: "Name")
var name: String
@Property(title: "Phone")
var phoneNumber: String
@Property(title: "Email")
var email: String
// ... all the other properties
}
I couldn't find guidance in the documentation for this specific situation. I've considered two approaches:
Add @Property variables to the AppEntity for each SwiftData model property and copy all values from the SwiftData model to the AppEntity in the AppEntity initializer — but I recall this being discouraged in previous WWDC sessions since it duplicates data and can become stale
Use @ComputedProperty to fetch the model and access the single properties — this seems like an alternative, but fetching the entire model just to access individual properties doesn't feel right
What is the recommended approach when SwiftData is the data source?
Thank you!
Hi all,
I have setup my app to use SwiftData with CloudKit sync. I have a production environment and development environment. I can reset the development environment for myself and all users in CloudKit console, but I can't reset the production one as it's tried to users' iCloud accounts, so I've added a button in-app for that feature. In the onboarding of my app, I pre-seed the DB with some default objects, which should be persisted between app install. The issue I'm running into is that I'm unable to force-pull these models from iCloud during the onboarding of a clean re-install, which leads to the models later appearing as duplicates once the user has been on the app for a few minutes and it has pulled from their iCloud account. If anyone has any suggestions on how to handle this issue, I would greatly appreciate it.
I have an iOS app (1Address) which allows users to share their address with family and friends using CloudKit Sharing.
Users share their address record (CKRecord) via a share link/url which when tapped allows the receiving user to accept the share and have a persistent view into the sharing user's address record (CKShare).
However, most users when they recieve a sharing link do not have the app installed yet, and so when a new receiving user taps the share link, it prompts them to download the app from the app store.
After the new user downloads the app from the app store and opens the app, my understanding is that the system (iOS) will/should then vend to my app the previously tapped cloudKitShareMetadata (or share url), however, this metadata is not being vended by the system. This forces the user to re-tap the share link and leads to some users thinking the app doesn't work or not completing the sharing / onboarding flow.
Is there a workaround or solve for this that doesn't require the user to tap the share link a second time?
In my scene delegate I am implementing:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {...}
And also
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {...}
And also:
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {...}
And:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {...}
Unfortunately, none of these are called or passed metadata on the initial app run after install. Only after the user goes back and taps a link again can they accept the share.
This documentation: https://developer.apple.com/documentation/cloudkit/ckshare says that adding the CKSharingSupported key to your app's Info.plist file allows the system to launch your app when a user taps or clicks a share URL, but it does not clarify what should happen if your app is being installed for the first time.
This seems to imply that the system is holding onto the share metadata and/or url, but for some reason it is not being vended to the app on first run.
Open to any ideas here for how to fix and I also filed feedback: FB20934189.
I work on an app that saves data to the Documents folder in the users iCloud Drive. This uses the iCloud -> iCloud Documents capability with a standard container.
We've noticed an issue where a user will delete the apps data by doing to Settings > {Name} > iCloud > Storage > App Name > select "delete data from iCloud", and then our app can no longer write to or create the Documents folder.
Once that happens, we get this error:
Error Domain=NSCocoaErrorDomain Code=513 "You don't have permission to save the file "Documents" in the folder "iCloud~your~bundle~identifier"." UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/iCloud~your~bundle~identifier/Documents, NSURL=file:///private/var/mobile/Library/Mobile%20Documents/iCloud~your~bundle~identifier/Documents, NSUnderlyingError=0x1102c7ea0 {Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied"}}
This is reproducible using the sample project here https://developer.apple.com/documentation/uikit/synchronizing-documents-in-the-icloud-environment.
Steps to reproduce in that project:
Tap the plus sign in the top right corner to create a new document
Add a document name and tap "Save to Documents"
Go to Settings > {Name} > iCloud > Storage > SimpleiCloudDocument App Name > select "delete data from iCloud"
Reopen the app and repeat steps 1-2
Observe error on MainViewController+Document.swift:59
Deleting and reinstalling the app doesn't seem to help.
Topic:
App & System Services
SubTopic:
iCloud & Data
I have a new app I am working on, it uses, a container id like com.me.mycompany.FancyApp.prod, the description in the app is My Fancy App. When I deploy the app via TestFlight on a real device, the sync seems to work, but when I view iCloud->Storage-List, I see my app icon, and the name "prod". Where did the name prod come from? It should be My Fancy App, which is the actual name of the App.
Hi everyone,
In the simple app below, I have a QueryView that has LazyVStack containing 100k TextField's that edit the item's content. The items are fetched with a @Query. On launch, the app will generate 100k items. Once created, when I press any of the TextField's , a severe hang happens, and every time I type a single character, it will cause another hang over and over again.
I looked at it in Instruments and it shows that the main thread is busy during the duration of the hang (2.31 seconds) updating QueryView. From the cause and effect graph, the update is caused by @Observable QueryController <Item>.(Bool).
Why does it take too long to recalculate the view, given that it's in a LazyVStack? (In other words, why is the hang duration directly proportional to the number of items?)
How to fix the performance of this app? I thought adding LazyVStack was all I need to handle the large dataset, but maybe I need to add a custom pagination with .fetchLimit on top of that? (I understand that ModelActor would be an alternative to @Query because it will make the database operations happen outside of the main thread which will fix this problem, but with that I will lose the automatic fetching of @Query.)
Thank you for the help!
import SwiftData
import SwiftUI
@main
struct QueryPerformanceApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(for: [Item.self], inMemory: true)
}
}
}
@Model
final class Item {
var name: String
init(name: String) {
self.name = name
}
}
struct ItemDetail: View {
@Bindable var item: Item
var body: some View {
TextField("Name", text: $item.name)
}
}
struct QueryView: View {
@Query private var items: [Item]
var body: some View {
ScrollView {
LazyVStack {
ForEach(items) { item in
VStack {
ItemDetail(item: item)
}
}
}
}
}
}
struct ContentView: View {
let itemCount = 100_000
@Environment(\.modelContext) private var context
@State private var isLoading = true
var body: some View {
Group {
if isLoading {
VStack(spacing: 16) {
ProgressView()
Text("Generating \(itemCount) items...")
}
} else {
QueryView()
}
}
.task {
for i in 1...itemCount {
context.insert(Item(name: "Item \(i)"))
}
try? context.save()
isLoading = false
}
}
}
I'm building a SwiftUI app with SwiftData and want to centralize both query logic and related actions in a manager class. For example, let's say I have a reading app where I need to track the currently reading book across multiple views.
What I want to achieve:
@Observable
class ReadingManager {
let modelContext: ModelContext
// Ideally, I'd love to do this:
@Query(filter: #Predicate<Book> { $0.isCurrentlyReading })
var currentBooks: [Book] // ❌ But @Query doesn't work here
var currentBook: Book? {
currentBooks.first
}
func startReading(_ book: Book) {
// Stop current book if any
if let current = currentBook {
current.isCurrentlyReading = false
}
book.isCurrentlyReading = true
try? modelContext.save()
}
func stopReading() {
currentBook?.isCurrentlyReading = false
try? modelContext.save()
}
}
// Then use it cleanly in any view:
struct BookRow: View {
@Environment(ReadingManager.self) var manager
let book: Book
var body: some View {
Text(book.title)
Button("Start Reading") {
manager.startReading(book)
}
if manager.currentBook == book {
Text("Currently Reading")
}
}
}
The problem is @Query only works in SwiftUI views. Without the manager, I'd need to duplicate the same query in every view just to call these common actions.
Is there a recommended pattern for this? Or should I just accept query duplication across views as the intended SwiftUI/SwiftData approach?
Hi all,
I’m trying to understand SwiftData’s runtime semantics around optional to-many relationships, especially in the context of CloudKit-backed models.
I ran into behavior that surprised me, and I’d like to confirm whether this is intended design or a potential issue / undocumented behavior.
Minimal example
import SwiftUI
import SwiftData
@Model
class Node {
var children: [Node]? = nil
var parent: Node? = nil
init(children: [Node]? = nil, parent: Node? = nil) {
self.children = children
self.parent = parent
print(self.children == nil)
}
}
struct ContentView: View {
var body: some View {
Button("Create") {
_ = Node(children: nil)
}
}
}
Observed behavior
If @Model is not used, children == nil prints true as expected.
If @Model is used, children == nil prints false.
Inspecting the macro expansion, it appears SwiftData initializes relationship storage using backing data placeholders and normalizes to-many relationships into empty collections at runtime, even when declared as optional.
CloudKit context
From the SwiftData + CloudKit documentation:
“The iCloud servers don’t guarantee atomic processing of relationship changes, so CloudKit requires all relationships to be optional.”
Because of this, modeling relationships as optional is required when syncing with CloudKit, even for to-many relationships.
This is why I’m hesitant to simply switch the model to a non-optional [Node] = [], even though that would match the observed runtime behavior.
Questions
Is it intentional that optional to-many relationships in SwiftData are never nil at runtime, and instead materialize as empty collections?
If so, is Optional<[Model]> effectively treated as [Model] for runtime access, despite being required for CloudKit compatibility?
Is the defaultValue: nil in the generated Schema.PropertyMetadata intended only for schema/migration purposes rather than representing a possible runtime state?
Is there a recommended modeling pattern for CloudKit-backed SwiftData models where relationships must be optional, but runtime semantics behave as non-optional?
I’m mainly looking to ensure I’m aligning with SwiftData’s intended design and not relying on behavior that could change or break with CloudKit sync.
Thanks in advance for any clarification!
When I used to do Migrations, I always used ETL and then push to a dev system to review/test before going production.
The migration support is SwiftData is fine for a little tweak.
I might as well just just use new schema and context and write the custom code than use the SwiftData migration support.
Experiencing a crash that is only reproducible on TestFlight or AppStore version of the app, note this does not happen when running from Xcode.
I've isolated the problem to sort argument being added to @Query that fetches a model that sorts based on inherited property.
To reproduce:
@Model
class SuperModel {
var createdAt: Date = .now
}
@available(macOS 26.0, *)
@Model
class SubModel: SuperModel {
}
@Query(sort: \SubModel.createdAt, animation: .default) private var models: [SubModel]