Identify and resolve synchronization issues when working with NSPersistentCloudKitContainer.
View Technote TN3164 >
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
In the system of iOS17, I store the login information through NSUserDefaults, but after the application is closed, I open it again, and the information stored in NSUserDefaults does not exist. I have not found the specific reason. I did not find this problem in the system before ios 17. This issue only appeared after iOS 17.
I remember long time ago I could click on a button in Xcode to launch a Web page to manage iCloud data. But now I cannot find the button.
I want to make icloud backup using SwiftData in VisionOS and I need to use SwiftData first but I get the following error even though I do the following steps
I followed the steps below
I created a Model
import Foundation
import SwiftData
@Model
class NoteModel {
@Attribute(.unique) var id: UUID
var date:Date
var title:String
var text:String
init(id: UUID = UUID(), date: Date, title: String, text: String) {
self.id = id
self.date = date
self.title = title
self.text = text
}
}
I added modelContainer
WindowGroup(content: {
NoteView()
})
.modelContainer(for: [NoteModel.self])
And I'm making inserts to test
import SwiftUI
import SwiftData
struct NoteView: View {
@Environment(\.modelContext) private var context
var body: some View {
Button(action: { // new Note
let note = NoteModel(date: Date(), title: "New Note", text: "")
context.insert(note)
}, label: {
Image(systemName: "note.text.badge.plus")
.font(.system(size: 24))
.frame(width: 30, height: 30)
.padding(12)
.background(
RoundedRectangle(cornerRadius: 50)
.foregroundStyle(.black.opacity(0.2))
)
})
.buttonStyle(.plain)
.hoverEffectDisabled(true)
}
}
#Preview {
NoteView().modelContainer(for: [NoteModel.self])
}
I get this error even though everything is turned on, how can I solve it?
It works on IOS but I get this error on VisionOS
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _recoverFromPartialError:forStore:inMonitor:]_block_invoke(2726): <NSCloudKitMirroringDelegate: 0x600003b0c700>: Found unknown error as part of a partial failure: <CKError 0x600000cce460: "Permission Failure" (10/2007); server message = "Invalid bundle ID for container"; op = 7FE8CD52A7B2E8FC; uuid = D8D1F2C9-2C01-45B2-BECC-270CA5520D55; container ID = "iCloud.Multitools">
let previewContainer:ModelContainer = {
do {
let config = ModelConfiguration(cloudKitDatabase: .private("iCloud.Multitools"))
let container = try ModelContainer(for: NoteModel.self, configurations: config)
return container
}
catch {
fatalError("Error to create container")
}
}()
Hello! I'm using SwiftData in my app with the @available(iOS 17.0, *) annotation.
It runs perfectly on every simulator runnning on iOS 17.0.1, but I encounter a weird memory error I can't seem to solve on devices running on iOS 17.3.1.
I'm sorta baffled right now. I am trying to wonder how I might detect a updated SQL Store in an older app.
have a baseline app, and create a SQL-based repository
in an updated app, change the model and verify that you can see the updated model version. Using lightweight migration
re-run the older app (which will inherit the newer SQL repository).
YIKES - no error when creating the NSPersistenStoreCoordinator!
Nothing in the metadata to imply the store is newer than the model:
[_persistentStoreCoordinator metadataForPersistentStore:store]
My question: is there any way to detect this condition?
David
I'm having some trouble with the following function from the CKSyncEngineDelegate protocol.
func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext,
syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? {
The sample code from the documentation is
func nextRecordZoneChangeBatch(
_ context: CKSyncEngine.SendChangesContext,
syncEngine: CKSyncEngine
) async -> CKSyncEngine.RecordZoneChangeBatch? {
// Get the pending record changes and filter by the context's scope.
let pendingChanges = syncEngine.state.pendingRecordZoneChanges
.filter { context.options.zoneIDs.contains($0) }
// Return a change batch that contains the corresponding materialized records.
return await CKSyncEngine.RecordZoneChangeBatch(
pendingChanges: pendingChanges) { self.recordFor(id: $0) }
}
init?(pendingChanges: [CKSyncEngine.PendingRecordZoneChange], recordProvider: (CKRecord.ID) -> (CKRecord?)) works fine for the sample app which only has one record type, but it seems incredible inefficient for my app which has a dozen different record types. The recordProvider gives you a CKRecord.ID, but not the CKRecord.RecordType. Searching each record type for a matching ID seems very inefficient.
Doesn't the CKSyncEngine.PendingRecordZoneChange contain an array of CKRecords, not just CKRecord.IDs? According to the documentation CKSyncEngine.RecordZoneChangeBatch has a recordsToSave property, but Xcode reports 'CKSyncEngine.PendingRecordZoneChange' has no member 'recordsToSave'
I'm looking for someway to get the CKRecords from syncEngine.state.pendingRecordZoneChanges.
I implemented to my app iCloud backup and restore services. Basically App uploads it's document directory files to iCloud using NSURL containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; and [fileManager copyItemAtURL:fileURL toURL:destinationURL error:&error]; and everything has been OK to upload files. By checking my iCloud account, it has taken the space needed to store those files.
However, when I changed my decice, I'm unable to get those files back. At first, files had .icloud extra extension added, but now the function just reports only 5 files from few hunder. The function used to get backup files is:
`- (NSArray) getBackupFiles {
NSError *error;
NSURL *containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (!containerURL) return nil;
// NSArray *keysArray = [[NSArray alloc] initWithObjects: nil];
NSArray *resultsArray = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:containerURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:&error];
if (error) {
[self myAlertView:error.localizedDescription];
return nil;
}
else return resultsArray;
}
Any ideas, what went wrong?`
Hey everyone,
I'm trying to get the user's name to display in a welcome screen, but unfortunatelly no success so far.
For that, I'm using CKContainer.default().shareParticipant(forUserRecordID: recordID).userIdentity.nameComponents, but the returned nameComponents are empty, despite receiving no error and accountStatus of .available.
Here's my code:
struct Helper {
static func getUserInformation() async throws -> Models.UserInfo {
let container = CKContainer.default()
let accountStatus = try! await container.accountStatus()
var accountStatusDescription = ""
switch accountStatus {
case .couldNotDetermine:
accountStatusDescription = "couldNotDetermine"
case .available:
accountStatusDescription = "available"
case .restricted:
accountStatusDescription = "restricted"
case .noAccount:
accountStatusDescription = "noAccount"
case .temporarilyUnavailable:
accountStatusDescription = "temporarilyUnavailable"
@unknown default:
accountStatusDescription = "default"
}
print("[Helper] CKContainer accountStatus: \(accountStatusDescription) ")
// Prints "[Helper] CKContainer accountStatus: available"
do {
let recordID = try await container.userRecordID()
let id = recordID.recordName
let participant = try await container.shareParticipant(forUserRecordID: recordID)
guard let nameComponents = participant.userIdentity.nameComponents else {
throw Models.ServiceError.userIdentityUnknownName
}
print("[Helper] CKShare.Participant nameComponents \(nameComponents)")
// Prints "[Helper] CKShare.Participant nameComponents - "
print("[Helper] CKShare nameComponents.givenName \(String(describing: nameComponents.givenName))")
print("[Helper] CKShare nameComponents.nickname \(String(describing: nameComponents.nickname))")
print("[Helper] CKShare nameComponents.familyName \(String(describing: nameComponents.familyName))")
print("[Helper] CKShare nameComponents.namePrefix \(String(describing: nameComponents.namePrefix))")
print("[Helper] CKShare nameComponents.nameSuffix \(String(describing: nameComponents.nameSuffix))")
print("[Helper] CKShare nameComponents.middleName \(String(describing: nameComponents.middleName))")
let name = PersonNameComponentsFormatter().string(from: nameComponents)
return Models.UserInfo(
id: id,
name: name
)
} catch {
throw error
}
}
}
Other than that, this project is using CloudKit for persistence through SwiftData and everything seems to be duly setup and working fine.
Any idea of what I might be missing? Any user permissions required? As far as I understood, from iOS 17 on and using this code, no permissions are required anymore but I may be wrong.
Any hint / help would be much appreciated!
Cheers,
Jorge
I have couple of questions regarding app and schema version management related to SwiftData migration.
For instance, it's common for the schema to change from V1 to V2 when updating an app from V1 to V2 and V3.
This process seems reasonable to me.
here's Moreover, if the versions go up to V10, does this mean I need to separate and organize stages for all migration codes, such as from V1 to V2, V2 to V3, up to V10, MigrationStages and Models for such exception handling?
It was too wordy, here are my summary questions.
1. Can I limit to specific versions in my workspace?(e.g V1->2->3->1->2 .. so on)
2. if its not possible, For extensive versioning up to V10, should migration codes be organized for every incremental update for handling exceptions?
3. If I need to permanently record every stages and model, what improvements could be made to this structure?, can I get some useful tips?
What interfaces do I use to propagate a CloudKit change in a shared zone to a notification/badge to all participants in the shared zone?
Assume I have a 'League' that is the root object in a shared zone and that N Players are members of the league. One of the players, the 'organizer', schedules a 'Game' that is open to any of the players. When the organizer creates the game (in the league's shared zone) and it is mirrored in CloudKit, how can the other players see it (as a timely notification)?
I already observe .NSPersistentStoreRemoteChange on NSPersistentStoreCoordinator and NSPersistentCloudKitContainer.eventChangedNotification on NSPersistentCloudKitContainer. Are these delivered in the background? Can/Should they generate a 'local/remote' notification for handling at the AppDelegate level? How?
Do I need to use a CKDatabaseSubscription looking for CD_Game records directly? (I'd rather not; because then I'd have a potential race between the remote iCloud database(s) and the local CoreData)
Hi, I have issue when I use swift data in development and I'm beginner. I have two basic classes.
@Model
class User{
@Attribute(.unique) var id: UUID;
var account: String;
var password: String;
var name: String;
var role: Role
@Relationship(inverse: \Transaction.tranInitiator)
var transcations = [Transaction]()
init(account: String, password: String, name: String, role: Role) {
self.id = UUID()
self.account = account
self.password = password
self.name = name
self.role = role
}
}
//
@Model
class Transaction{
@Attribute(.unique) var tranId: UUID;
var tranName: String;
var tranCash: Float;
var tranDate: Date;
@Relationship var tranInitiator: User;
// init block
}
}
and when I am going to fetch items I use predictor like
let predictor = #Predicate<Transaction>{ tran in
tran.tranInitiator == user
}
fetchDescriptorTrans = FetchDescriptor(predicate: predictor)
let transList = try? contextTransaction?.fetch(fetchDescriptorTrans)
if(transList!.isEmpty){return []}
else{
return transList!
}
compiler shows error
Cannot convert value of type 'PredicateExpressions.Equal<PredicateExpressions.KeyPath<PredicateExpressions.Variable, User>, PredicateExpressions.Value>' to closure result type 'any StandardPredicateExpression'
I don't know why
I am using the public cloud database to store my application data, this data is accessed by all users of the application, but at some point it is necessary for a user who did not create a respective data in the database to delete it, but from what I read in the documentation this is not possible, only with a permission. How do I allow a user to change or delete any data created by another user in the public cloud database?
I’m having an issue when two of my SwiftData models have a one-to-many relationship and I have them synced via CloudKit.
To be clear, I’ve met all of the requirements to make it iCloud friendly and sync to work. I followed this https://www.hackingwithswift.com/quick-start/swiftdata/how-to-sync-swiftdata-with-icloud, and can confirm I’ve done it correctly because initially I was seeing this crash on startup when I had not:
Thread 1: Fatal error: Could not create ModelContainer: SwiftDataError(_error: SwiftData.SwiftDataError._Error.loadIssueModelContainer)
This is to say, the problem may be iCloud related but it’s not due to a wrong model setup. Speaking of which, these are models:
@Model
class Film {
var name: String = ""
var releaseYear: Int = 0
var director: Director? = nil
init(name: String, releaseYear: Int, director: Director) {
self.name = name
self.releaseYear = releaseYear
self.director = director
}
}
@Model
class Director {
var name: String = ""
@Relationship(deleteRule: .cascade, inverse: \Film.director)
var films: [Film]? = []
init(name: String, films: [Film]) {
self.name = name
self.films = films
}
}
I’ve set the delete rule for the relationship between Film and Director to be cascading because you can’t have a film without a director (to be clear, even when set as nullify, it doesn’t make a difference)
And this is the @main App definition:
@main
struct mvpApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Film.self,
Director.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
}
And this is the dummy ContentView:
struct ContentView: View {
var body: some View {
EmptyView()
.onAppear {
let newDirector = Director(name: "Martin Scorcese", films: [])
let film = Film(name: "The Wolf of Wall Street", releaseYear: 2019, director: newDirector)
newDirector.films!.append(film)
}
}
}
I create a Director with no films assigned. I then create a Film, and the append it to the Director’s [Film] collection.
The last step however causes a crash consistently:
There is a workaround that involves removing this line from the Film init():
self.director = director // comment this out so it’s not set in a Film’s init()
When I do this, I can append the (Director-less) Film to the Director’s [Film] collection.
Am I misunderstanding how these relationships should work in SwiftData/CloudKit? It doesn’t make any sense to me that when two models are paired together that only one of them has a reference to the relationship, and the other has no knowledge of the link.
The above is a minimum reproducible example (and not my actual application). In my application, I basically compromised with the workaround that initially appears to be without consequence, but I have begun to notice crashes happening semi-regularly when deleting models that I suspect must be linked to setting the foundations incorrectly.
This piece of code was working properly until I Introduced the Exercise Model. I have tried everything like using migration scheme, removing the app, cleaning build folder, trying on different devices and simulator. Nothing seems to work now. I can't get the app to launch.
struct LogbookApp: App {
@State private var sharedModelContainer: ModelContainer = {
let schema = Schema([
Workout.self,
Exercise.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch(let error) {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
MainCoordinator()
.view()
}
.modelContainer(sharedModelContainer)
}
}
The models look like this
@Model
final class Exercise {
var name: String
var workout: Workout?
init(name: String, workout: Workout? = nil) {
self.name = name
self.workout = workout
}
}
@Model
final class Workout {
@Attribute(.unique) var id = UUID()
@Relationship(deleteRule: .cascade) var exercises: [Exercise]?
var type: WorkoutType
var date: Date
var duration: Int
var customDescription: String
init(id: UUID = UUID(),
type: WorkoutType,
date: Date,
duration: Int,
customDescription: String = "",
exercises: [Exercise]? = []) {
self.id = id
self.type = type
self.date = date
self.duration = duration
self.customDescription = customDescription
self.exercises = exercises
}
}
I'm running into a crash on launch in Simulator with my visionOS that uses SwiftData in Xcode Version 15.3 (15E5202a).
This is printing to the console: SwiftData/DataUtilities.swift:1093: Fatal error: Unable to parse keypath for non-PersistentModel Type, and then it's crashing on _swift_runtime_on_report.
This worked fine in Xcode 15.2.
I'll return to Xcode 15.2 for now and report an issue, but is there anyone that can translate what this even means so I can fix it?
Thanks
Hi, I'm trying to make some changes to my SwiftData model and I want to add a new non-optional property to one of my model classes.
My current model was not part of a VersionedSchema so I first encapsulated it into one
public enum FeynnDataModelsSchemaV1: VersionedSchema {
public static var versionIdentifier: Schema.Version = .init(1, 0, 0)
public static var models: [any PersistentModel.Type] {
[FeynnDataModelsSchemaV1.Workout.self, FeynnDataModelsSchemaV1.Activity.self, FeynnDataModelsSchemaV1.ActivityRecord.self, FeynnDataModelsSchemaV1.WorkoutSession.self]
}
}
Then I run the app and everything works as expected.
Secondly, I create the V2 and add the new property:
public enum FeynnDataModelsSchemaV2: VersionedSchema {
public static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0)
public static var models: [any PersistentModel.Type] {
[FeynnDataModelsSchemaV2.Workout.self, FeynnDataModelsSchemaV2.Activity.self, FeynnDataModelsSchemaV2.ActivityRecord.self, FeynnDataModelsSchemaV2.WorkoutSession.self]
}
}
extension FeynnDataModelsSchemaV2 {
@Model
final public class Activity: Hashable {
...
public var activityType: ActivityType = ActivityType.traditionalStrengthTraining
...
}
}
Lastly, I create the schema migration plan and add it to my modelContainer:
public enum FeynnDataModelsMigrationPlan: SchemaMigrationPlan {
public static var schemas: [VersionedSchema.Type] = [
FeynnDataModelsSchemaV1.self,
FeynnDataModelsSchemaV2.self
]
public static var stages: [MigrationStage] = [migrateV1toV2]
public static var migrateV1toV2 = MigrationStage.custom(fromVersion: FeynnDataModelsSchemaV1.self, toVersion: FeynnDataModelsSchemaV2.self, willMigrate: {moc in
let activities = try? moc.fetch(FetchDescriptor<Activity>())
print("\(activities?.count ?? 919191991)")
}, didMigrate: {moc in
let activities = try? moc.fetch(FetchDescriptor<Activity>())
print("\(activities?.count ?? 88888888)")
activities?.forEach { activity in activity.activityType = .traditionalStrengthTraining }
try? moc.save()
})
}
if let container = try? ModelContainer(
for: Workout.self, Activity.self, ActivityRecord.self, WorkoutSession.self,
migrationPlan: FeynnDataModelsMigrationPlan.self,
configurations: ModelConfiguration(cloudKitDatabase: .automatic)) {
self.container = container
} else {
self.container = try ModelContainer(
for: Workout.self, Activity.self, ActivityRecord.self, WorkoutSession.self,
migrationPlan: FeynnDataModelsMigrationPlan.self,
configurations: ModelConfiguration(cloudKitDatabase: .none))
}
After running this, the application runs as expected, but as soon as I render a view that references Activity.activityType the app crashes trying to get the value for my existing activities given that it is nil, pointing out that the migration stage was not ran?
None of the print statements in the didMigrate or willMigrate can be found within the logs either.
I have tried several approaches creating more VersionedSchemas and I run into more issues such as Cannot use stuck migration with an unknown model version.
I feel like the current way of handling schema versions of SwiftData is very confusing and opaque for the developer to know what is going on. I don't know if the underlying database is picking the un-versioned schema, the V1 or the V2.
Is there anything I'm doing wrong? What can I do apart from making the new property optional and having a computed property that unwraps it giving it a default value when nil?
Thank you!
i am developing a SwiftUI App, where i need to work with relatively large amounts of data sets. While processing these data i had some issues with my app crashing randomly. As i was debugging this situation for a while i found out that dataraces were the cause for these crashes. That is why i decided to use an actor for these things..
As the actor takes care of concurrent threads, i was not having any crashes anymore, BUT, now i have to deal with some memory leaks!
i've created a simple demo project to reproduce these leaks.
my view:
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
// @Query private var items: [Item]
var body: some View {
VStack {
Button(action: {
Task { await testImport() }
}, label: {
Text("Import")
})
}
}
}
the function:
func testImport() async {
let actorX = testActor(container: self.modelContext.container)
await actorX.cleanUp()
// create dummy data:
var dummyArray: [Int] = []
for i in 0...1300 {
dummyArray.append(i)
}
await actorX.saveAssets(with: dummyArray)
dummyArray = []
print("Checkpoint")
}
the actor:
actor testActor {
public var modelContainer: ModelContainer
public var modelExecutor: any ModelExecutor
private var context: ModelContext { modelExecutor.modelContext }
public init(container: ModelContainer) {
self.modelContainer = container
let context = ModelContext(modelContainer)
modelExecutor = DefaultSerialModelExecutor(modelContext: context)
}
func cleanUp() {
print("starting cleanup...")
do {
try context.delete(model: Item.self)
print("cleanup: table LocalAsset truncated")
} catch {
print("Failed to clear all LocalAsset data.")
}
}
func saveAssets(with array: [Int]) {
for i in 0..<array.count {
let foo = array[i]
let newItem = Item(timestamp: Date(), dummyInt: foo)
context.insert(newItem)
}
try? context.save()
}
}
And Here's a screenshot of Xcode's Instruments Leak tool:
i hope somebody has any idea how to get rid of those leaks..
Hey there,
I am having a question which hopefully someone can answer.
I am developing an App and wanted to store Images in SwiftData with the
@External Storage Attribute.
I get it to work with a single image and also with an array of images but...saving 1 Image shows a storage space of about 38 bytes but when I store an array of 3 Images the storage in the SQL Database (Blob Type) increases to over 8 Million Bytes?
Is this a bug or is this what it is?
I am having a direction for a workaround but it would be more convenient if it would work with SwiftData for an array of images same as it does for 1 image.
Thanks for any clarification.