Hi,
I'm updating my app from CoreData to SwiftData and came across an issue. My app has multiple users so in CoreData and I assigned each a myID value set to an UUID so I could use UserDefaults to set the preferred user on app load. When switching over I noticed the PersistentIdentifier value and got excited as I could fetch the matching entity with modelContext.model(for: yourID). I decided to use that instead so I updated my UserDefaults code from UUID to this:
@Published var selectedUserID: PersistentIdentifier? {
didSet {
UserDefaults.standard.set(selectedUserID, forKey: "selectedUserID")
}
}
init() {
self.selectedUserID = UserDefaults.standard.object(forKey: "selectedUserID") as? PersistentIdentifier ?? nil
}
This code compiles and, of course the id is currently set to nil. My issue now is when I try to assign a user to it ar my app crashes and I get the following error:
Attempt to set a non-property-list object SwiftData.PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-coredata://6FE80FC9-0B4C-491E-8093-DED37A619F1B/EnteredUser/p834), implementation: SwiftData.PersistentIdentifierImplementation) as an NSUserDefaults/CFPreferences value for key selectedUserID
Should I go back to an additional UUID field in my user model and find it that way or is there a way to use the PersistentIdentifier value with my UserDefaults?
Thanks for any tips.
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
We're doing some disaster recovery management / risk management and a point-of-failure for our app is if we lose access to our bundle id.
From my understanding, secure keychain items are scoped to your bundle ID as well as iCloud files stored under the app with 'hidden' scope.
Losing our bundle ID is a scenario we want to eliminate completely from our threat/disaster modelling.
Is this a realistic concern we should have?
How do I get Upsert to work in SwiftData?
I have a model:
@Model
class Address: Identifiable {
@Attribute(.unique)
var id: Int = 0
var number: String = ""
var street: String?
var city: String?
}
When I insert a duplicate record with the same data attributes (specifically overlapping id) and execute modelContext.save() I receive a EXC_BREAKPOINT.
let addressA = Address(id: 1, number: "2300", street: "E Main St", city: "Small Town" );
modelContext.insert(addressA)
try? modelContext.save()
let addressAv2 = Address(id: 1, number: "2300", street: "E Main St", city: "Small Town" );
modelContext.insert(addressAv2)
try? modelContext.save() // This fails
Is there a better way to handle updates?
Is there any tool to automatically migrate data & data structure from Google Firebase to CloudKit?
The documentation states that you can use either finishDeferredLightweightMigrationTask or finishDeferredLightweightMigration to complete a deferred migration at a later time. However when I tried finishDeferredLightweightMigrationTask, I noticed that the metadata for the store isn't updated. How would you know when the migration has completed and you can stop calling finishDeferredLightweightMigrationTask?
The reason I was reaching for the individual migration task approach is that I thought it would be better for handling an expired background task since I could check for cancellation after each step.
Hi
I'm confused. Today my icloud drive folder started downloading files to my macbook. These files were stored in the cloud. There was a cloud icon with a down arrow next to them. Now over 11 gigabytes of files have now downloaded from the cloud. The icon is gone. Consequently, they have been uploaded to the macbook. How do I get them back into the cloud ? I'm confused about the actions and icons.
Friends,
I have created an App in SwiftData that uses CloudKit to sync between devices. I am using Xcode 15.0.1, and iOS 17.1.1. For further context, I am calling modelContext.save() with every change that I want to synchronise, and this seems to call a CloudKit save, as expected, within the development environment.
The synchronisation works great in the development environment with no issues, it updates fairly quickly and I use @Query to manage SwiftUI updates in the most direct way possible. It is a complex model with many relationships, and I call @Query and filter instances to get to the instance required, rather than using traditional Bindings, as many tutorials and WWDC have suggested.
When I switch to the production environment the app synchronises between devices great using the production CloudKit container. I have set up push notification certificates, and these seem to work. The issues is that, after a few minutes, the synchronisation stops happening between devices.
When synchronisation is working, Device 1 initially saves to CloudKit, and you can see the items update in the CloudKit console. When synching stops working, Device 1 stops saving, and the CloudKit console stops updating.
If I then delete and install the app on Device 1 from TestFlight, you can see that it starts saving again, with updates to the CloudKit console as expected.
But, Device 2 does not begin updating again unless you delete the app on that device, and install again.
This behaviour seems to suggest that both devices become logged out or deregistered from CloudKit after a short period of time as a consequence of some unknown process within CloudKit or SwiftData. When logged out, each device cannot write to, or read from, CloudKit.
Now, the problem with trying to fix the issue is that the registration with CloudKit, silent push notifications, and fetches, are all managed "under the hood" by SwiftData.
In the production environment, you cannot configure any registration state or refresh registration within your code, because you don't have access to the parts of SwiftData that manage this.
I wanted to find out if this is an issue that has been encountered by other users, and if anyone can help with a solution, or perhaps a debugging strategy that I can use to find out what is happening.
Thank you all in advance.
I made simple school & student model with RelationShip
import SwiftUI
import SwiftData
@Model final class School {
var name: String
@Relationship(deleteRule: .cascade, inverse: \Student.school)
var studentList: [Student] = [Student]()
init(name: String) {
self.name = name
}
}
@Model final class Student {
var name: String
var school: School?
init(name: String) {
self.name = name
}
}
~
struct SchoolView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \School.name) var schools: [School]
@Query(sort: \Student.name) var students: [Student]
var body: some View {
NavigationStack {
List {
ForEach(schools) { school in
NavigationLink {
StudentView(school: school)
} label: {
HStack {
Text(school.name)
Spacer()
Text("\(school.studentList.count)")
}
}
.swipeActions {
Button(role: .destructive) {
deleteSchool(school)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
.onDelete(perform: deleteSchools(at:))
}
.navigationTitle("Home")
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
addSchool()
} label: {
Image(systemName: "plus")
}
}
ToolbarItem(placement: .topBarLeading) {
VStack(alignment: .leading) {
Text("School: \(schools.count)")
Text("Student: \(students.count)")
}
}
}
}
}
private func randomString() -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<5).map { _ in letters.randomElement()! })
}
private func addSchool() {
let newSchool = School(name: randomString())
modelContext.insert(newSchool)
}
private func deleteSchools(at offsets: IndexSet) {
offsets.map { schools[$0] }.forEach(deleteSchool)
}
private func deleteSchool(_ school: School) {
modelContext.delete(school)
try? modelContext.save()
}
}
struct StudentView: View {
@Environment(\.modelContext) private var modelContext
var school: School
var body: some View {
List {
ForEach(school.studentList) { student in
Text(student.name)
}
.onDelete(perform: deleteStudents(at:))
}
.navigationTitle("Home")
.navigationBarTitleDisplayMode(.inline)
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
addStudent()
} label: {
Image(systemName: "plus")
}
}
}
}
private func addStudent() {
let newSchool = Student(name: randomString())
modelContext.insert(newSchool)
newSchool.school = school
school.studentList.append(newSchool)
}
private func randomString() -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<5).map { _ in letters.randomElement()! })
}
private func deleteStudents(at offsets: IndexSet) {
withAnimation {
offsets.forEach {
let student = school.studentList[$0]
modelContext.delete(student)
try? modelContext.save()
}
}
}
}
I try my best to follow the SampleTrips app from Apple developer.
When I delete one of the schools, then all the students in the school should be deleted. But the student count never changes.
Deleting the student itself works well - the student count decreases.
Thank you!
Hi,
My app has been receiving a huge increase in the number of CKHTTPStatus=503 errors over the past couple of months. I created a thread before, and also a Feedback (FB13300807) over a month ago, but I haven't gotten any assistance on this, and am wondering if there is any better way to get the attention of a CloudKit engineer who might be able to help.
From my users, I was able to print out the error code and error userInfo in the console:
error.code == 15 (the same as CKErrorServerRejectedRequest),
UserInfo={ContainerID=, CKHTTPStatus=503, RequestUUID=17C6B9B9-35DD-411B-8AED-7A497075D228, OperationID=5285362CCD2DDB32}}, CKHTTPStatus=503}
How can I get this issue addressed? A lot of users are reporting this issue and it's creating a big support burden.
I have a shopping list app and the data model is very simple, just 5 entities. These have names List, Product, Item, Section and Data. For CoreData I was able to use the auto-generated classes and gave my own names for the classes (prefixing my entity names with CD - e.g CDList)
However with SwiftData I don't have that capability. My @Model classes are named List, Data etc and obviously these clash with the standard Swift/SwiftUI classes. Even the much quoted example app has the same issue a task todo list with an entity named Task, which would clash with Swift's own Task class.
Is the correct/only approach that I don't use the @Model macro but roll my own SwiftData classes - e.g inline the macros, or is there a way I can give the @Model classes my own name (e.g SDList, and leave the CoreData entity names as they are). I don't really want to rename my entities if at all avoidable.
I tried to practice SwiftData by creating a LinkedList. However, contrary to my expectations.
even though a.next == b
but b.next != nil, b.next == a. Why is that?
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
Button {
var a = Item(value: 0)
var b = Item(value: 1)
a.next = b
modelContext.insert(a)
} label: {
Text("+++")
}
@Model
final class Item {
var value: Int
weak var next: Item?
init(value: Int, next: Item? = nil) {
self.value = value
self.next = next
}
}
this is whole code
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
ZStack {
VStack {
ForEach(items){item in
Button {
modelContext.delete(item)
} label: {
Text(String(item.value))
}
}
}
}
VStack {
Button {
var a = Item(value: 0)
var b = Item(value: 1)
a.next = b
modelContext.insert(a)
// modelContext.insert(b)
// let fetch = FetchDescriptor<Item>(
// predicate: #Predicate{$0.value == 0}
// )
// let data = try! modelContext.fetch(fetch)
// data[0].next = b
} label: {
Text("+++")
}
Button {
for i in items {
modelContext.delete(i)
}
} label: {
Text("---")
}
Button {
for i in items {
print(i.value)
print(i.next)
}
} label: {
Text("Print")
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}
Very simple one. how do I add “create“ permissions for my CloudKit security roles. For some reason “create“ is greyed out when I try to check the box for _creator
Starting today around 2PM MST I started receiving emails from users reporting their CloudKit synced data was 'completely gone'...
They are reporting no error messages... so that means the CKQuery on the private database is completing without error, but simply not returning any previously saved records...
This appears to be only occurring to a percentage of my users and I cannot replicate it on my device (I'm using iOS 17.1.2.
Are any other developers experiencing this?!
SwiftData supports the unique attribute. Unfortunately that one is not available when using CloudKit for SwiftData.
I'm looking for some help (maybe an example) or best practices how to implement the same behavior.
I needed help creating the predicate in order to query my swift data model for the record with the largest integer. Is this possible, and is it any more efficient than just fetching all the data and then doing the filtering?
I've created a container, enabled CloudKit in xCode and added code to reference the container and save. Every time I run the app I get this error when I close the game:
Snapshot request 0x2814167c0 complete with error: <NSError: 0x28144df80; domain: BSActionErrorDomain; code: 1 ("response-not-possible")>
The app is not saved and i cannot see any action with the container. My app is built in Unity with C# code and builds with no errors.
I would appreciate and assistance!
I'm developing an app with dynamic intents fetched from a SwiftData container with data that is configured inside the app. However, the following happens when .fetch() is run during widget configuration.
When creating a new instance of the model and inserting it into the container, it disappears when the app is relaunched.
When creating a new instance of the model and inserting it into the container, and then attempting to delete that instance, the app crashes. This error shows up Could not cast value of type 'Swift.Optional<Any>' (0x2003ddfc0) to 'Foundation.UUID' (0x200442af8).
This is a snippet of the query code.
func entities(for identifiers: [ExampleEntity.ID]) async throws -> [ExampleEntity] {
let modelContext = ModelContext(Self.container)
let examples = try? modelContext.fetch(FetchDescriptor<ExampleModel>())
return examples?.map {
ExampleEntity(example: $0)
} ?? []
}
SwiftData works as expected as long as the .fetch() is not called in the EntityQuery.
Hi, I followed the instructions here: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
but when trying to get access to each file inside the selected folder (tried iCloud and iPhone) it always fails. Instead, if I specify a type of file on the Controller (say .audio) I can read the file fine...
Did anything change which is not documented in that page? Do I have to set any other permission or capability? (I have iCloud Documents)
Thanks!!
I have v3 models in coredata (model: Event, Lecture), and make new model in v4 with swiftdata but model name, property is different (model: EventModel, LectureModel).
For migration, I add V3Schema, V4Schema and MigrationPlan.
enum V4Schema: VersionedSchema {
static var models: [any PersistentModel.Type] = [LectureModel.self, EventModel.self ]
static var versionIdentifier = Schema.Version(4, 0, 0)
}
enum V3Schema: VersionedSchema {
static var models: [any PersistentModel.Type] = [Event.self, Lecture.self]
static var versionIdentifier = Schema.Version(3, 5, 2)
}
enum ModelMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] = [V3Schema.self, V4Schema.self]
static var stages: [MigrationStage] = [migrateV3ToV4]
}
extension ModelMigrationPlan {
static let migrateV3ToV4 = MigrationStage.custom(fromVersion: V3Schema.self,
toVersion: V4Schema.self,
willMigrate: willMigrate,
didMigrate: { _ in Log.debug(message: "Migration Complete") })
}
private func willMigrate(context: ModelContext) throws {
try migrateLectures(context: context)
try migrateEvents(context: context)
try context.save()
}
private func migrateEventTypes(context: ModelContext) throws {
// create new v4 event model using v3 event model.
}
private func migrateLectures(context: ModelContext) throws {
// create new v4 lecture model using v3 lecture model.
}
static let release: ModelContainer = {
let v4Schema = Schema(V4Schema.models)
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "app group id")
let databaseURL = containerURL?.appending(path: "**.sqlite")
let configuration = ModelConfiguration("**",
schema: v4Schema,
url: databaseURL!,
cloudKitDatabase: .private("**"))
#if DEBUG
do {
try autoreleasepool {
let desc = NSPersistentStoreDescription(url: configuration.url)
let opts = NSPersistentCloudKitContainerOptions(containerIdentifier: configuration.cloudKitContainerIdentifier!)
desc.cloudKitContainerOptions = opts
desc.shouldAddStoreAsynchronously = false
if let model = NSManagedObjectModel.makeManagedObjectModel(for: V4Schema.models) {
let container = NSPersistentCloudKitContainer(name: "**", managedObjectModel: model)
container.persistentStoreDescriptions = [desc]
container.loadPersistentStores(completionHandler: { _, err in
if let err {
Log.error(message: "Store open failed: \(err.localizedDescription)")
}
})
try container.initializeCloudKitSchema()
Log.debug(message: "Initialize Cloudkit Schema")
if let store = container.persistentStoreCoordinator.persistentStores.first {
try container.persistentStoreCoordinator.remove(store)
}
}
}
} catch {
Log.error(message: "Failed: \(error.localizedDescription)")
}
#endif
return try! ModelContainer(for: v4Schema, migrationPlan: ModelMigrationPlan.self, configurations: configuration)
}()
But when I run, I got error message.
CoreData: CloudKit: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _scheduleAutomatedExportWithLabel:activity:completionHandler:]_block_invoke(3508): <NSCloudKitMirroringDelegate: 0x1036b1540> - Finished automatic export - ExportActivity - with result: <NSCloudKitMirroringResult: 0x1035da810> storeIdentifier: ***** success: 0 madeChanges: 0 error: Error Domain=NSCocoaErrorDomain Code=134407 "Request '*****' was cancelled because the store was removed from the coordinator." UserInfo={NSLocalizedFailureReason=Request '****' was cancelled because the store was removed from the coordinator.}
I don't know why store was removed from the coordinator.
Any have solutions?
I'm currently using Swiftdata to store data for an app I've deployed to the app store.
The problem is that the app does not build when I add a case of the Enum type to the model, so I decided to apply a MigrationPlan. I also decided that it is not a good idea to store the Enum type itself because of future side effects.
The app is deployed in the app store with a model without a VersionedSchema, so the implementation is complete until we modify it to a model with a VersionedSchema.
This is Version1.
public enum TeamSchemaV1: VersionedSchema {
public static var versionIdentifier: Schema.Version = .init(1, 0, 0)
public static var models: [any PersistentModel.Type] {
[TeamSchemaV1.Team.self,
TeamSchemaV1.Lineup.self,
TeamSchemaV1.Player.self,
TeamSchemaV1.Human.self]
}
@Model
public final class Lineup {
...
public var uniform: Uniform
...
}
And you're having trouble migrating to Version2. The change is to rename the Uniform to DeprecatedUniform (the reason for the following change is to migrate even if it's just a property name)
public enum TeamSchemaV2: VersionedSchema {
public static var versionIdentifier: Schema.Version = .init(1, 0, 1)
public static var models: [any PersistentModel.Type] {
[TeamSchemaV2.Team.self,
TeamSchemaV2.Lineup.self,
TeamSchemaV2.Player.self,
TeamSchemaV2.Human.self]
}
@Model
public final class Lineup {
...
@Attribute(originalName: "uniform")
public var deprecatedUniform: Uniform
...
When you apply this plan and build the app, EXC_BAD_ACCESS occurs.
public enum TeamMigrationPlan: SchemaMigrationPlan {
public static var schemas: [VersionedSchema.Type] {
[TeamSchemaV1.self, TeamSchemaV2.self]
}
public static var stages: [MigrationStage] {
[migrateV1toV2]
}
public static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: TeamSchemaV1.self,
toVersion: TeamSchemaV2.self)
}
I'm currently unable to migrate the data, which is preventing me from updating and promoting the app. If anyone knows of this issue, I would really appreciate your help.