Posting here to see if folks have workarounds or if I have a misunderstanding of SwiftData supported types.
In adopting SwiftData, I have swiftData properties of collection type (Array or Set - both have this issue). E.g:
@Model
final class Item {
var timestamp: Date
var strings = ["aa", "bb"]
var display: String {
strings.joined(separator: " ")
}
init(timestamp: Date) {
self.timestamp = timestamp
}
}
So far in development I haven't had issues on iOS 17, but on the iOS 18 betas 4-5 the app logs show the following error:
"fault: Could not materialize Objective-C class named "Array" from declared attribute value type "Array<String>" of attribute named strings"
It happens immediately in my app when creating an object with a collection attribute.
In a minimal test example, the error log appears only after a few minutes and doesn't seem to affect the template app's basic functionality.
Anyone else running into this?
Was filed as FB14397250
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
Created
Hi,
I'm struggling with SwiftData and the components for migration and could really use some guidance. My specific questions are
Is it possible to go from an unversioned schema to a versioned schema?
Do all @Model classes need to be converted?
Is there one VersionedSchema for the entire app that handles all models or one VersionedSchema per model?
What is the relationship, if any, between the models given to ModelContainer in a [Schema] and the models in the VersionedSchema in a [any PersistentModel.Type]
I have an app in the AppStore. I use SwiftData and have four @Models defined. I was not aware of VersionedSchema when I started, so they are unversioned. I want to update the model and am trying to convert to a VersionedSchema. I've tried various things and can't even get into the migration plan yet. All posts and tutorials that I've come across only deal with one Model, and create a VersionedSchema for that model.
I've tried to switch the one Model I want to update, as well as switching them all. Of course I get different errors depending on what configuration I try.
It seems like I should have one VersionedSchema for the app since there is the static var models: [any PersistentModel.Type] property. Yet the tutorials I've seen create a TypeNameSchemaV1 to go with the @Model TypeName.
Which is correct? An AppNameSchemaV1 which defines four models, or four TypeNameSchemaV1?
Any help will be much appreciated
Whilst all current answers to this question indicate the depreciation of CKModifyBadgeOperation, all of them had advised up until 2022 that it could still be used due to Apple not having a replacement Api. Upon running the following code:
badgeReset.modifyBadgeCompletionBlock = { (error) -> Void in
if error != nil {
print("Error resetting badge: \(error!)")
}
}
CKContainer.default().add(badgeReset)
When run I receive the following error:
Error resetting badge: <CKError 0x3001ddf50: "Invalid Arguments" (12/1017); "CKModifyBadgeOperation is no longer supported">
And from testing following this, the badge count incrementation issue continues, indicating that this has been completely invalidated and cannot be used at all.
Even with UNUserNotificationCenter.current().setBadgeCount(0) this only clears the badge count temporarily.
I'm aware of the proposed "workaround" of creating an extension that manually keeps track of notification count & sets the badge accordingly when a notification is received. However I'm trying to ascertain if as of this current point in time there is now no way whatsoever to clear the badge count on the Cloudkit sever level?
[Submitted as FB14860454, but posting here since I rarely get responses in Feedback Assistant]
In a simple SwiftData app that adds items to a list, memory usage drastically increases as items are added. After a few hundred items, the UI lags and becomes unusable.
In comparison, a similar app built with CoreData shows only a slight memory increase in the same scenario and does NOT lag, even past 1,000 items.
In the SwiftData version, as each batch is added, memory spikes the same amount…or even increases! In the CoreData version, the increase with each batch gets smaller and smaller, so the memory curve levels off.
My Question
Are there any ways to improve the performance of adding items in SwiftData, or is it just not ready for prime time?
Example Projects
Here are the test projects on GitHub if you want to check it out yourself:
PerfSwiftData
PerfCoreData
Hi,
when using CKSynEgine it is the responsibility of the app to implement CKSyncEngineDelegate. One of the methods of CKSyncEngineDelegate is nextFetchChangesOptions. The implementation of this method should return a batch of CKRecords so that CKSyncEngine can do the syncing whenever it thinks it should sync. A simple implementation might look like this:
func nextRecordZoneChangeBatch(
_ context: CKSyncEngine.SendChangesContext,
syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch?{
await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: syncEngine.state.pendingRecordZoneChanges) { recordID in
// here we should fetch to local representation of the value and map it to a CKRecord
}
}
The problem I am having is as follows:
If the CKRecords I am returning in a batch have dependencies between each other (using CKRecord.Reference or the parent property) but are not part of the same batch, the operation could fail. And as far as I understand, there is no way to prevent this situation because:
A:
The batch I can return is limited in size. If the number of CKRecords is too large, I have to split them into multiple batches.
B:
Splitting them is arbitrary, since I only have the recordID at this point, and there is no way to know about the dependencies between them just by looking at the recordID.
So basically my question is: how should the implementation of nextRecordZoneChangeBatch look like to handle dependencies between CKRecords?
Topic:
App & System Services
SubTopic:
iCloud & Data
I'm using SwiftData with an @Model and am also using an @ModelActor. I've fixed all concurrency issues and have migrated to Swift 6. I am getting a console error that I do not understand how to clear. I get this error in Swift 6 and Swift 5. I do not experience any issue with the app. It seems to be working well. But I want to try to get all issues taken care of. I am using the latest Xcode beta.
error: the replacement path doesn't exist:
"/var/folders/1q/6jw9d6mn0gx1znh1n19z2v9r0000gp/T/swift-generated-sources/@_swiftmacro_17MyAppName14MyModelC4type18_PersistedPr> opertyfMa.swift"
After installing iOS 18.1 and iPados 18.1 we get a consuste failure when trying to add to our one to many data model. This was working well until we installed 18.1
when trying to add a entry to the many relationship we get this error
Illegal attempt to map a relationship containing temporary objects to its identifiers.
Topic:
App & System Services
SubTopic:
iCloud & Data
I’m working on a project where I’m using CKSyncEngine to sync different types of SwiftData models, specifically User and Organization, to CloudKit. Here’s how I schedule these models to be synced:
For the User model:
let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [.saveRecord(user.recordID)]
syncEngine.state.add(pendingRecordZoneChanges: pendingSaves)
For the Organization model:
let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [.saveRecord(organization.recordID)]
syncEngine.state.add(pendingRecordZoneChanges: pendingSaves)
The problem arises in my CKSyncEngineDelegate's nextRecordZoneChangeBatch method where from CKRecord.ID alone I need to create the actual CKRecord that will be synced to CloudKit. This recordID alone doesn’t provide enough information to determine 1) in which local model table I need to fetch actual data to build whole CKRecord; and 2) what to put in CKRecord.recordType - whether it’s a User or an Organization.
Question:
What is the best practice for passing or determining the model type (e.g., User or Organization) in nextRecordZoneChangeBatch? How should I handle this in a way that effectively differentiates between the different model types being synced?
Any advice or examples would be greatly appreciated!
Few ideas:
embed the Model type in RecordID.recordName string, but this makes my recordNames longer (like resource_29af3932).
fetch data by recordID in all local persistent storage, but this seems slow and there is constraint that User and Organization IDs should never be the same.
introduce lookup table where from CKRecordID I can look up model type.
Somehow extend CKRecordID to add model type field?
I'm using SwiftData to persist my items in storage. I used .modelContext to pass in my shared context, and on iOS 18 (both on a physical device and a simulator), I discovered a bug where SwiftData doesn't automatically save my data. For example, I could add a new item, go to the next screen, change something that reloads a previous screen, and SwiftData just forgets the item that I added. Please find the fully working code attached.
While writing this post, I realized that if I use .modelContainer instead of .modelContext, the issue is solved. So I have two questions:
It seems like .modelContainer is the go-to option when working with SwiftData, but why did an issue occur when I used .modelContext and passed in a shared container? When should we use .modelContext over .modelContainer?
What was the bug? It's working fine in iOS 17, but not in iOS 18. Or is this expected?
Here's the fully working code so you can copy and paste:
import SwiftUI
import SwiftData
typealias NamedColor = (color: Color, name: String)
extension Color {
init(r: Double, g: Double, b: Double) {
self.init(red: r/255, green: g/255, blue: b/255)
}
static let namedColors: [NamedColor] = [
(.blue, "Blue"),
(.red, "Red"),
(.green, "Green"),
(.orange, "Orange"),
(.yellow, "Yellow"),
(.pink, "Pink"),
(.purple, "Purple"),
(.teal, "Teal"),
(.indigo, "Indigo"),
(.brown, "Brown"),
(.cyan, "Cyan"),
(.gray, "Gray")
]
static func name(for color: Color) -> String {
return namedColors.first(where: { $0.color == color })?.name ?? "Blue"
}
static func color(for name: String) -> Color {
return namedColors.first(where: { $0.name == name })?.color ?? .blue
}
}
@main
struct SwiftDataTestApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
@AppStorage("accentColor") private var accentColorName: String = "Blue"
var body: some Scene {
WindowGroup {
NavigationStack {
HomeView()
}
.tint(Color.color(for: accentColorName))
}
.modelContainer(sharedModelContainer) // This works
// .modelContext(ModelContext(sharedModelContainer)) // This doesn't work
}
}
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
struct HomeView: View {
@State private var showSettings = false
@Environment(\.modelContext) var modelContext
@AppStorage("accentColor") private var accentColorName: String = "Blue"
@Query private var items: [Item]
var body: some View {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
Button {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
} label: {
Image(systemName: "plus")
.frame(maxWidth: .infinity)
.frame(maxHeight: .infinity)
}
}
.navigationTitle("Habits")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: { showSettings = true }) {
Label("", systemImage: "gearshape.fill")
}
}
}
.navigationDestination(isPresented: $showSettings) {
colorPickerView
}
}
private var colorPickerView: some View {
Form {
Section(header: Text("Accent Color")) {
Picker("Accent Color", selection: $accentColorName) {
ForEach(Color.namedColors, id: \.name) { namedColor in
Text(namedColor.name)
.tag(namedColor.name)
.foregroundColor(namedColor.color)
}
}
.pickerStyle(.wheel)
}
}
.navigationTitle("Settings")
}
}
I have been using the basic NSPersistentContainer with 100k+ records for a while now with no issues. The database size can fluctuate a bit but on average it takes up about 22mb on device.
When I switch the container to NSPersistentCloudKitContainer, I see a massive increase in size to ~150mb initially. As the sync engine uploads records to iCloud it has ballooned to over 600mb on device. On top of that, the user's iCloud usage in settings reports that it takes up 1.7gb in the cloud.
I understand new tables are added and history tracking is enabled but the size increase seems a bit drastic. I'm not sure how we got from 22mb to 1.7gb with the exact same data.
A few other things that are important to note:
I import all the 100k+ records at once when testing the different containers. At the time of the initial import there is only 1 relation (an import group record) that all the records are attached to.
I save the background context only once after all the records and the import group have been made and added to the context.
After the initial import, some of these records may have a few new relations added to them over time. I suppose this could be causing some of the size increase, but its only about 20,000 records that are updated.
None of the records include files/ large binary data.
Most of the attributes are encrypted.
I'm syncing to the dev iCloud environment.
When I do make a change to a single attribute in a record, CloudKit reports that every attribute has been modified (not sure if this is normal or not )
Also, When syncing to a new device, the sync can take hours - days. I'm guessing it's having to sync both the new records and the changes, but it exponentially gets slower as more records are downloaded. The console will show syncing activity, but new records are being added at a slower rate as more records are added. After about 50k records, it grinds to a halt and while the console still shows sync activity, only about 100 records are added every hour.
All this to say i'm very confused where these issues are coming from. I'm sure its a combination of how i've setup my code and the vast record count, record history, etc.
If anyone has any ideas it would be much appreciated.
Is it ok to have latency about 4 sec? The amount of downloaded data is less than 1 MB. Maybe I need to setup an index for every field requested?
I'm still getting this error (SwiftData/ModelContext.swift:3253: Fatal error: Failed to identify a store that can hold instances of SwiftData._KKMDBackingData<Presents_2024.Item> from [:]) in Xcode 16.1 Beta (16B5001e).
The app works for a limited amount of time and then crashes with this error. It looks like the SwiftData model isn't being created properly and when a context is saved it crashes.
Can you tell me if this error will be fixed in the next beta?
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
Xcode
Xcode Sanitizers and Runtime Issues
Core Data
SwiftData
Hi guys. Can someone please confirm this bug so I report it? The issue is that SwiftData relationships don't update the views in some specific situations on devices running iOS 18 Beta. One clear example is with CloudKit. I created a small example for testing. The following code creates two @models, one to store bands and another to store their records. The following code works with no issues. (You need to connect to a CloudKit container and test it on two devices)
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var records: [Record]
var body: some View {
NavigationStack {
List(records) { record in
VStack(alignment: .leading) {
Text(record.title)
Text(record.band?.name ?? "Undefined")
}
}
.toolbar {
ToolbarItem {
Button("Add Record") {
let randomNumber = Int.random(in: 1...100)
let newBand = Band(name: "New Band \(randomNumber)", records: nil)
modelContext.insert(newBand)
let newRecord = Record(title: "New Record \(randomNumber)", band: newBand)
modelContext.insert(newRecord)
}
}
}
}
}
}
@Model
final class Record {
var title: String = ""
var band: Band?
init(title: String, band: Band?) {
self.title = title
self.band = band
}
}
@Model
final class Band {
var name: String = ""
var records: [Record]?
init(name: String, records: [Record]?) {
self.name = name
self.records = records
}
}
This view includes a button at the top to add a new record associated with a new band. The data appears on both devices, but if you include more views inside the List, the views on the second device are not updated to show the values of the relationships. For example, if you extract the row to a separate view, the second device shows the relationships as "Undefined". You can try the following code.
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var records: [Record]
var body: some View {
NavigationStack {
List {
ForEach(records) { record in
RecordRow(record: record)
}
}
.toolbar {
ToolbarItem {
Button("Add Record") {
let randomNumber = Int.random(in: 1...100)
let newBand = Band(name: "New Band \(randomNumber)", records: nil)
modelContext.insert(newBand)
let newRecord = Record(title: "New Record \(randomNumber)", band: newBand)
modelContext.insert(newRecord)
}
}
}
}
}
}
struct RecordRow: View {
let record: Record
var body: some View {
VStack(alignment: .leading) {
Text(record.title)
Text(record.band?.name ?? "Undefined")
}
}
}
Here I use a ForEach loop and move the row to a separate view. Now on the second device the relationships are nil, so the row shows the text "Undefined" instead of the name of the band.
I attached an image from my iPad. I inserted all the information on my iPhone. The first three rows were inserted with the first view. But the last two rows were inserted after I extracted the rows to a separate view. Here you can see that the relationships are nil and therefore shown as "Undefined". The views are not updated to show the real value of the relationship.
This example shows the issue with CloudKit, but this also happens locally in some situations. The system doesn't detect updates in relationships and therefore doesn't refresh the views.
Please, let me know if you can reproduce the issue. I'm using Mac Sequoia 15.1, and two devices with iOS 18.0.
Since the iOS 18 and Xcode 16, I've been getting some really strange SwiftData errors when passing @Model classes around.
SwiftData/BackingData.swift:409: Fatal error: This model instance was destroyed by calling ModelContext.reset and is no longer usable. PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-coredata://D0F0E233-8D1D-4020-924B-BA56959140FD/ListModel/p10), implementation: SwiftData.PersistentIdentifierImplementation)
The same issue also happens when I try to retrieve a model from the ModelContext using its PersistentIdentifier and try to do anything with it. I have no idea what could be causing this.
This is my actor
@ModelActor
actor ListCrudOperations:ObservableObject{
func add(list:ListModel){
modelContext.insert(list)
try? modelContext.save()
}
func delete(identifier:PersistentIdentifier){
guard let list = self[identifier, as: ListModel.self] else {
print("error")
return
}
if list.listType == .task{
list.reminders!.forEach { reminder in
TaskModel.delete(modelContext: modelContext, reminder: reminder)
NotificationService.deleteReminders(name: reminder.title!, Id: reminder.id)
}
}
else if list.listType == .subscription {
list.subscription!.forEach({ sub in
Subscription.delete(modelContext: modelContext, subscription: sub)
NotificationService.deleteReminders(name: sub.ServiceName, Id: sub.id)
})
}
else if list.listType == .link {
list.links!.forEach ({link in
Links.delete(modelContext: modelContext, link: link)
NotificationService.deleteNotificationForLink(title: link.name, linkID: link.id)
}
)
}
modelContext.delete(list)
try? modelContext.save()
}
func addReminder(reminder:TaskModel, identifier:PersistentIdentifier){
guard let list = self[identifier, as: ListModel.self] else {
print("error")
return
}
list.reminders!.append(reminder)
reminder.list = list
try? modelContext.save()
}
func addSubscription(subscription:Subscription, identifier:PersistentIdentifier){
guard let list = self[identifier, as: ListModel.self] else {
print("error")
return
}
list.subscription!.append(subscription)
subscription.list = list
try? modelContext.save()
}
func addLink(link:Links, identifier: PersistentIdentifier) {
guard let list = self[identifier, as: ListModel.self] else {
print("error")
return
}
list.links?.append(link)
link.list = list
try? modelContext.save()
}
func fetchListByType(type:ListType) -> [ListModel] {
let type = SwiftTaskSchemaV8.ListModel.ListType(rawValue: type.rawValue)!
let fetchDescriptor = FetchDescriptor<ListModel>()
do {
let list = try modelContext.fetch(fetchDescriptor)
let list2 = try list.filter(#Predicate { $0.listType == type })
return list2
}catch{
return []
}
}
func fetchListsForMultipleTypes(_ types: [ListType]) -> [ListModel] {
return types.flatMap { type in fetchListByType(type: type) }
}
func fetchAllList() -> [ListModel] {
let fetchDescriptor = FetchDescriptor<ListModel>(sortBy: [.init(\.createdDate)])
do {
let list = try modelContext.fetch(fetchDescriptor)
return list
}catch{
return []
}
}
}```
and this is how i am calling it
@Environment(.modelContext) private var context
let listOperation = ListCrudOperations(modelContainer: context.container)
let list = ListModel(name: name, color: self.color, icon: self.icon, listType: ListModel.ListType(rawValue: picked.rawValue)!)
Task {
await listOperation.add(list: list)
await MainActor.run{
withAnimation(.bouncy){
self.list.append(list)
}
CrashServices.shared.addLogs(message: "folder added")
}
}
I have an app which uses SwiftData and CloudKit all works fine and well. Now I wanted to implement a feature which lets the user know that there are data incoming from the cloud and they have to wait a little bit for the data to show up. Furthermore my app needs to do some data sanitation when it starts up. This sanitation should only be done after the CloudKit updates are processed.
So is there a way that my app can know when CloudKit is doing updates and when it is finished? I was looking for some kind of notification but couldn’t find any info on that.
I don’t need to know which data are updated or process the data. I just want to get notified when a sync starts and when it has ended. Actually it would suffice to know when a sync is finished.
In my app, I worked with ios18 by default and I had no issue, everything was working fine. However, when I wanted to test it with an iOS 17.5 simulator or real device, it is unusable because it crash when object with a relationship are created.
on the first line you can see my relationship, and under it it is the extended relationship macro unmodifiable.
has someone already encountered this? any clue of a hide modification of relationship working behavior in ios18?
this is really annoying cause it make me unable to reduce the minimum deployment target of my app to ios17
I'm building an application with SwiftUI and SwiftData. Up until a couple days ago, everything was working fine. Then, Xcode auto-updated to v16 in the background, and the next time I opened Xcode and tried to build my app it wouldn't build anymore, citing some errors in expanding the SwiftData @Model macro on one of my objects. Attached are the errors specifically, shown where Xcode shows them (in the expanded @Model macro). In text, they are:
Instance method 'access(_:keyPath:)' requires that 'Member' conform to 'Observable'
Cannot convert value of type 'Risers.Member' to expected argument type 'Member'
Instance method 'withMutation(of:keyPath:_:)' requires that 'Member' conform to 'Observable'
Cannot convert value of type 'Risers.Member' to expected argument type 'Member'
Here is the SwiftData class in full:
import SwiftData
import SwiftUI
@Model
class Member: Identifiable, Hashable {
var chorus: Chorus?
var id = UUID()
var firstName: String
var lastName: String
var fullName: String {
"\(firstName) \(lastName)"
}
var voicePart: Int
var voicePartString: String? {
chorus?.voicePartType.prettyName(forPart: voicePart)
}
@Attribute(.externalStorage) var pictureData: Data
init(chorus: Chorus? = nil,
firstName: String = "",
lastName: String = "",
voicePart: Int = 1,
pictureData: Data = Data()) {
self.chorus = chorus
self.firstName = firstName
self.lastName = lastName
self.voicePart = voicePart
self.pictureData = pictureData
}
init(member: Member) {
self.chorus = member.chorus
self.firstName = member.firstName
self.lastName = member.lastName
self.voicePart = member.voicePart
self.pictureData = member.pictureData
}
I tried building again on Xcode 15.4, and it still builds successfully there. Xcode 16.1 beta has not made a difference. Is this my fault, or is Xcode 16 broken?
Hello, everyone!
I'm using CloudKit JS with a React SPA to allow users from a mobile app to access their data in a web browser. Currently, the project is still under development so there are no public users beside my team.
The way I've integrated CK JS in my app is via their CDN, importing the required url in my index.html file.
However, I'm having issues with the Authentication using Apple Sign In. While the Sign In and Sign Out buttons work correctly for me and my teammates, the session is not persisted for everyone. Actually, I'm the only one from me team that does not have to log in every day.
I have the following configuration function:
export const configureCloudKit = () =&gt; {
window.CloudKit.configure({
locale: 'en-us',
containers: [
{
containerIdentifier: CONTAINER_ID,
apiTokenAuth: {
apiToken: API_TOKEN,
persist: true,
signInButton: {
id: 'apple-sign-in-button',
theme: 'black',
},
signOutButton: {
id: 'apple-sign-out-button',
theme: 'black',
},
},
environment: 'development',
},
],
});
};
As you can see, I'm using the persist:true option so there shouldn't be any issues with having a persistent session.
From my research, I found that CloudKit JS sets a cookie called iCloud.com.myContainerName and if I delete that cookie, when I reload the browser, the session is indeed lost. This happens for all my teammates, same cookie and same behavior.
Nevertheless, I also found three cookies that are not present for any of my teammates but me (using Google Chrome). Those are called:
X-APPLE-WEBAUTH-AC-PARTITION
X-APPLE-WEBAUTH-AC-SERVERINFO
X-APPLE-WEBAUTH-AC-TOKEN
But even if I delete those cookies, the session is not lost for me.
Does anyone know whether I'm doing something wrong with the configuration?
Or if there are something I'm not taking into account regarding the cookies handling in my project?
Some users of my app are reporting total loss of data while using the app.
This is happening specifically when they enable iCloud sync.
I am doing following
private func setupContainer(enableICloud: Bool) {
container = NSPersistentCloudKitContainer(name: "")
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
guard let description: NSPersistentStoreDescription = container.persistentStoreDescriptions.first else {
fatalError()
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
if enableICloud == false {
description.cloudKitContainerOptions = nil
}
container.loadPersistentStores { description, error in
if let error {
// Handle error
}
}
}
When user clicks on Toggle to enable/disable iCloud sync I just set the description.cloudKitContainerOptions to nil and then user is asked to restart the app.
Apart from that I periodically run the clear history
func deleteTransactionHistory() {
let sevenDaysAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
let purgeHistoryRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: sevenDaysAgo)
let backgroundContext = container.newBackgroundContext()
backgroundContext.performAndWait {
try! backgroundContext.execute(purgeHistoryRequest)
}
}
Hello everyone,
I'm working on an iOS app that uses CloudKit for data synchronization. I'm encountering an issue where my app can't find the "JournalPrompt" record type in the public database. Here's the relevant code and error messages (I'm using placeholders like [APP_NAME] or [CONTAINER_IDENTIFIER]):
private func fetchPromptsFromiCloud() {
let container = CKContainer(identifier: "[CONTAINER_IDENTIFIER]")
let publicDatabase = container.publicCloudDatabase
// Create a predicate to query for the specific record
let predicate = NSPredicate(format: "recordID.recordName == %@", "B6663053-FC2E-4645-938B-9FA528D59663")
let query = CKQuery(recordType: "JournalPrompt", predicate: predicate)
publicDatabase.perform(query, inZoneWith: nil) { [weak self] (records, error) in
if let error = error as? CKError {
if error.code == .unknownItem {
print("JournalPrompt record type does not exist or the specific record was not found in the public database.")
} else {
print("Error fetching record from iCloud public database: \(error)")
}
return
}
guard let record = records?.first else {
print("No record found with the specified ID in the public database.")
return
}
print("Found record in public database:")
print("Record ID: \(record.recordID.recordName)")
print("Text: \(record["text"] as? String ?? "No text")")
print("Creation Date: \(record.creationDate ?? Date())")
print("Used Count: \(record["usedCount"] as? Int ?? 0)")
print("Is Default: \(record["isDefault"] as? Bool ?? false)")
}
}
Error
When I run this code, I get the following error:
Error fetching record from iCloud public database: <CKError 0x600000c072a0: "Invalid Arguments" (12/1009); "Invalid predicate: recordKey (recordID.recordName) contains invalid characters">
I've also implemented a function to check the CloudKit schema:
func checkCloudKitSchema() {
checkDatabase(scope: .private)
checkDatabase(scope: .public)
}
private func checkDatabase(scope: CKDatabase.Scope) {
let container = CKContainer(identifier: "[CONTAINER_IDENTIFIER]")
let database = scope == .private ? container.privateCloudDatabase : container.publicCloudDatabase
print("Checking \(scope == .private ? "private" : "public") database")
database.fetchAllRecordZones { (zones, error) in
if let error = error {
print("Error fetching record zones: \(error)")
return
}
print("Available record zones in \(scope == .private ? "private" : "public") database:")
zones?.forEach { zone in
print("- \(zone.zoneID.zoneName)")
}
let query = CKQuery(recordType: "JournalPrompt", predicate: NSPredicate(value: true))
database.perform(query, inZoneWith: nil) { (records, error) in
if let error = error as? CKError, error.code == .unknownItem {
print("JournalPrompt record type does not exist in the \(scope == .private ? "private" : "public") database.")
} else if let error = error {
print("Error fetching records from \(scope == .private ? "private" : "public") database: \(error)")
} else if let records = records, !records.isEmpty {
print("JournalPrompt record type exists in the \(scope == .private ? "private" : "public") database.")
print("Fetched \(records.count) JournalPrompt records:")
for record in records {
print("Record ID: \(record.recordID.recordName)")
print("Fields:")
record.allKeys().forEach { key in
print(" - \(key): \(type(of: record[key]))")
}
print("---")
}
} else {
print("JournalPrompt record type exists in the \(scope == .private ? "private" : "public") database, but no records found.")
}
}
}
}
When I run this, I get:
Checking public database Available record zones in public database:
_defaultZone JournalPrompt record type does not exist in the public database.
CloudKit Database Setup
I've set up my CloudKit Database as follows:
And my data model is as follows:
Despite this setup, my app can't seem to find or interact with the JournalPrompt record type. I've double-checked that my app's identifier matches the one in the CloudKit dashboard, and I've verified that the record type name is spelled correctly.
Questions:
Why might my app be unable to find the JournalPrompt record type, even though it's defined in the CloudKit dashboard?
Is there anything wrong with my query or error handling that could be causing this issue?
Are there any common pitfalls or setup steps I might have missed when integrating CloudKit?
Any insights or suggestions would be greatly appreciated.
I really appreciate any help you can provide.