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.
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
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 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?
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?
Hi
I’m having real problems trying to get a simple “to do” type app working with cloudkit. It works fine with SwiftData but as soon as I add CloudKit I get lots of “container not found errors “ which I think relates to the relationships between my classes. If I strip out the group sort order class it works fine.
I’ve stripped the app back to basics to test - I want to be able to add a “task” (task data) to a “group list “ (group data) also also store the position of the task in that list (group sort order) as there may be lots of tasks in a list. The same task could also be in a different group list with a different position in the list (so another entry and value in group sort order) .. why does the following not work??
any help appreciated!
// TaskData.swift
// TaskOutApp
//
import Foundation
import SwiftData
`@Model
class TaskData: Identifiable, Equatable {
var id = UUID()
var title: String = "No Title"
var isDone: Bool = false
var isToday: Bool = false
var creationDate: Date = Date()
var doneDate: Date = Date()
var todayDate: Date = Date()
// Use an array of GroupSortOrder to maintain both group and sort order
var groupSortOrders: [GroupSortOrder]? = nil
init(id: UUID = UUID(), title: String = "No Title", isDone: Bool = false, isToday: Bool = false, creationDate: Date = Date(), doneDate: Date = Date(), todayDate: Date = Date(), groupSortOrders: [GroupSortOrder]? = nil) {
self.id = id
self.title = title
self.isDone = isDone
self.isToday = isToday
self.creationDate = creationDate
self.doneDate = doneDate
self.todayDate = todayDate
self.groupSortOrders = groupSortOrders
}
static func currentDateString() -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter.string(from: Date())
}
static func == (lhs: TaskData, rhs: TaskData) -> Bool {
lhs.id == rhs.id
}
}
@Model
class GroupData: Identifiable {
var id = UUID()
var title: String = "no title"
var icon: String = "no icon"
var creationDate: Date = Date()
var task: [TaskData]? = []
init(id: UUID = UUID(), title: String, icon: String, creationDate: Date = Date(), task: [TaskData] = []) {
self.id = id
self.title = title
self.icon = icon
self.creationDate = creationDate
self.task = task
}
static func currentDateString() -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter.string(from: Date())
}
}
@Model
class GroupSortOrder: Identifiable {
var id = UUID()
var group: GroupData? = nil
var sortOrder: Int = 0
init(id: UUID = UUID(), group: GroupData? = nil, sortOrder: Int = 0) {
self.id = id
self.group = group
self.sortOrder = sortOrder
}
}`
Hi, after upgrading to Xcode 16.0 I encountered the fact that when loading preview, I get an error:
Fatal error: Inverse already set to another relationship - secondAccount - cannot assign to - firstAccount
import Foundation
import SwiftData
import SwiftUI
@Model
class TransactionModel {
@Relationship(inverse: \AccountModel.accountTransactions) var firstAccount: AccountModel? /// <- here
@Relationship(inverse: \AccountModel.accountTransactions) var secondAccount: AccountModel? /// <- here
@Relationship(inverse: \DebtorModel.transactions) var debtor: DebtorModel?
@Relationship(inverse: \CategoryModel.transactions) var category: CategoryModel?
init(account: AccountModel? = nil, secondAccount: AccountModel? = nil, debtor: DebtorModel? = nil, category: CategoryModel? = nil) {
self.firstAccount = account
self.secondAccount = secondAccount
self.debtor = debtor
self.category = category
}
}
import SwiftData
import SwiftUI
@Model
final public class AccountModel: Identifiable, Hashable {
var accountId: UUID = UUID()
var accountTransactions: [TransactionModel]?
init(id: UUID = UUID()) {
self.accountId = id
}
}
Is this really not possible to implement? In Xcode 15, this worked both on the device and in the editor.
Thanks!
Hi, I have a swift data class that has an enum PromptFont as a property.
the data class is hosted in CloudKit.
upon launch, I started getting this error:
below is the property and the enum
when I released this app, there was no error, and it was building and running flawlessly.
when I build this on a simulator (iPadOS 17.5), it runs just fine
I'm not entirely sure if this API only shows which fields have changed (e.g., change.updatedAttributes), or if it also provides the updated values for those fields. Ultimately, my goal is to allow users to click in the app to view how a model has mutated over time, and I was wondering if this feature could facilitate that.
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
Doing a batch delete on a many-to-one relationship seems to throw this error
CoreData: error: Unhandled opt lock error from executeBatchDeleteRequest Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on Student/school and userInfo {
NSExceptionOmitCallstacks = 1;
NSLocalizedFailureReason = "Constraint trigger violation: Batch delete failed due to mandatory OTO nullify inverse on Student/school";
"_NSCoreDataOptimisticLockingFailureConflictsKey" = (
);
}
If I try to delete the School in the one-to-many relationship, both the school and the students are deleted as expected.
However, If I try to delete all students the error is thrown. I would expect all students to be removed, while keeping the School intact.
Do SwiftData support this?
import XCTest
import SwiftData
@Model
class School {
var name: String
@Relationship(deleteRule: .cascade, inverse: \Student.school)
var students: [Student] = []
init(name: String) {
self.name = name
}
}
@Model
class Student {
var name: String
var school: School?
init(name: String) {
self.name = name
}
}
final class Test: XCTestCase {
func testScenario() throws {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let modelContainer = try ModelContainer(for:
School.self,
Student.self,
configurations: config
)
let context = ModelContext(modelContainer)
context.autosaveEnabled = false
let school = School(name: "school")
context.insert(school)
let student1 = Student(name: "1")
let student2 = Student(name: "2")
context.insert(student1)
context.insert(student2)
student1.school = school
student2.school = school
XCTAssertEqual(school.students.count, 2)
XCTAssertEqual(student1.school?.id, school.id)
XCTAssertEqual(student2.school?.id, school.id)
try context.save()
let newContext = ModelContext(modelContainer)
// try newContext.delete(model: School.self) // This works
try newContext.delete(model: Student.self) // This one fails
}
}
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.
Given the code below the students array on the school is not being updated. Why?
Since the relationship is explicit and non-optional I would expect this to work.
import XCTest
import SwiftData
@Model
class School {
var name: String
@Relationship(deleteRule: .cascade, inverse: \Student.school)
var students: [Student]
init(name: String, students: [Student]) {
self.name = name
self.students = students
}
}
@Model
class Student {
var name: String
var school: School
init(name: String, school: School) {
self.name = name
self.school = school
}
}
final class Test: XCTestCase {
func testScenario() throws {
let modelContainer = try ModelContainer(for:
School.self,
Student.self
)
let context = ModelContext(modelContainer)
context.autosaveEnabled = false
let school = School(name: "school", students: [])
context.insert(school)
let student1 = Student(name: "1", school: school)
let student2 = Student(name: "2", school: school)
context.insert(student1)
context.insert(student2)
XCTAssertEqual(school.students.count, 2) // XCTAssertEqual failed: ("0") is not equal to ("2")
}
}
Users will receive a unique ID, if a user enters another user's ID they will go to a view where both have access to the information, being able to change, add, delete...
(Paired, available on App Store)
Public container is not secure, private with ckshare doesn't seem to work for what I would like, plus the content is very confusing
I need something that uses native Apple technologies to build this system.
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")
}
}
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.
I'm running into an odd case where a model's reverse relationship is sometimes not set despite the forward relationship being there.
If the app is closed and reopened however, the reverse relationship for previously added data works.
For example, given three models Shelf, Item and ItemDetails:
@Model final class Shelf {
@Relationship(deleteRule: .cascade, inverse: \Item.primaryShelf)
var items: [Item] = []
init() {}
}
@Model final class Item {
var primaryShelf: Shelf?
var timestamp: Date
@Relationship(deleteRule: .cascade, inverse: \ItemDetail.item)
public var detail: ItemDetail?
init(primaryShelf: Shelf) {
self.primaryShelf = primaryShelf
self.timestamp = .now
}
}
@Model final class ItemDetail {
var item: Item?
init(item: Item) { self.item = item }
}
Now I want to simply create a shelf, some items and some itemdetails.
@Test func testRelationshipsThroughInit() async throws {
let schema = Schema([Shelf.self, Item.self, ItemDetail.self])
let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
let container = try ModelContainer(for: schema, configurations: [config])
let modelContext = ModelContext(container)
let shelf = Shelf()
modelContext.insert(shelf)
for _ in 0..<10 {
let item = Item(primaryShelf: shelf)
modelContext.insert(item)
let itemDetail = ItemDetail(item: item)
modelContext.insert(itemDetail)
}
try modelContext.save()
let fetchDescriptor = FetchDescriptor<Shelf>()
let shelves = try modelContext.fetch(fetchDescriptor)
// fails with a random number between 0 and 9 typically
#expect(shelves.first?.items.count == 10)
}
There seem to be two ways that this problem goes away. The first is changing the order of properties set in ItemDetail.init() so that the relationship is set after everything else:
@Model final class Item {
// ...
init(primaryShelf: Shelf) {
self.timestamp = .now
self.primaryShelf = primaryShelf
}
With this, everything seems to work fine. The other way seems to be manually setting the reverse relationship. So the loop above gets changed to:
for _ in 0..<10 {
let item = Item(primaryShelf: shelf)
modelContext.insert(item)
let itemDetail = ItemDetail(item: item)
modelContext.insert(itemDetail)
// add reverse relationship even though forward was set
shelf.items.append(item)
}
My question is, is this the expected behavior and If so, is there any place this is documented?
I have added core data to my project and below is the code block of how it's section in pbxproj file looks like.
However whenever I make any changes to the project i.e. changing build version or adding a deleting new files to the project, I have noticed "name = "sample-app.xcdatamodeld";" gets deleted automatically from the project file and I have to manually reverse that change.
Am I missing any setting for core data or is it a bug somewhere in XCode or Core Data? I am using XCode 15.
/* Begin XCVersionGroup section */
4683EC5B2C10F8B800A5081B /* sample-app.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
4683EC5C2C10F8B800A5081B /* sample-app.xcdatamodel */,
);
currentVersion = 4683EC5C2C10F8B800A5081B /* sample-app.xcdatamodel */;
name = "sample-app.xcdatamodeld";
path = "sample-app.xcdatamodeld";
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
I have a type which represents an ID. This ID comes from a backend as an untyped string. The Swift type looks something like this:
struct TypedID: Codable {
init(_ string: String) {
stringValue = string
}
init(from decoder: any Decoder) throws {
stringValue = try decoder.singleValueContainer().decode(String.self)
}
func encode(to encoder: any Encoder) throws {
try stringValue.encode(to: encoder)
}
let stringValue: String
}
If I use this type in a SwiftData @Model, or even as a property of another Codeable struct which is contained in the @Model, SwiftData fails to create instances of my model and emits the following error:
CoreData: error: CoreData: error: Row (pk = #) for entity '<@Model type>' is missing mandatory text data for property 'stringValue'
This issue goes away if I remove the custom implementation of init(from:) and encode(to:), but doing so makes the coded representation of my type an object instead of a string, which is not what I want.
/// JSON without custom implementations:
{
"stringValue": "<the ID>"
}
/// JSON with custom implementations:
"<the ID>"
Is there anything I can do to be able to serialize a Codable type with a custom coding implementation like this to a SwiftData model?
I'm encountering an issue when trying to store a SIMD3<Float> in a SwiftData model. Since SIMD3<Float> already conforms to Codable, I expected it to work. However, attempting to store a single SIMD3<Float> crashes with the following error:
Fatal error: Unexpected property within Persisted Struct/Enum: Builtin.Vec4xFPIEEE32
Interestingly, storing an array of vectors, [SIMD3<Float>], works perfectly fine. The issue only arises when trying to store a single SIMD3<Float>.
I’m not looking for a workaround (I can break the vector into individual floats in a custom codable struct to get by) , but I’d like to understand why storing a codable SIMD3<Float> in SwiftData results in this crash. Is this a limitation of SwiftData, or is there something I’m missing about how vectors are handled?
Any insights would be greatly appreciated!
I am seeking guidance on handling field-level schema changes in CKSyncEngine, specifically when introducing new fields in a subsequent app version.
The current CKSyncEngine documentation (https://developer.apple.com/documentation/cloudkit/cksyncengine) and the GitHub sample (https://github.com/apple/sample-cloudkit-sync-engine) provide clear instructions for managing changes to existing CKRecords. However, I am uncertain about the best approach for handling newly added fields in a new version of my app.
For example:
In version 1 of my app, I have a CKRecord named User with a field called name.
In version 2, I plan to add a new field, phone_number, to the User record. When version 1 (e.g., installed on a user's iPad) and version 2 (e.g., installed on the same user's iPhone) sync using CKSyncEngine, version 1 is unaware of the phone_number field.
My concern is how to ensure version 1 handles this scenario gracefully without blocking other sync events.
Additionally, when version 1 on the iPad is later updated to version 2, there will be no new sync events unless the "phone_number" field is modified again. This could result in the "phone_number" field never being synced to the iPad.
Could you please advise on the best practices for handling such cases to ensure seamless synchronization across different app versions?
Thank you for your assistance.