Hi,
if I have a @Model class there's always an id: PersistentIdentifier.ID underneath which, according to the current documentation "The value that uniquely identifies the associated model within the containing store.".
So I am wondering if it is (good) enough to rely on this attribute to uniquely identify @Model class entities, or if there are edge cases where it does not work (like maybe when using CloudKit)?
If anybody saw some information regarding this, please let me know :-)
Cheers,
Michael
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
I have an app that uses CoreData and I want to migrate to SwiftData. After following the Migrate to SwiftData session, I only need to point to my old Core Data file to read the old data and convert it to the new SwiftData format.
My question is how do I do this? Maybe worth mentioning is that my NSPersistentContainer(name: "Model") is different to my app name.
Possible Solution?
According to a Tweet by Donny Wals this is done this way:
By default a SwiftData ModelContainer will create its underlying storage in a file called default.store. If you want to change this so you can use an existing Core Data SQLite file, you can point your container to that file instead:
// point to your old sqlite file
let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite")
let config = ModelConfiguration(url: url)
modelContainer = try ModelContainer(for: [
Movie.self
], config)
My Tested Code
@main
struct SwiftData_TestApp: App {
let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite")
let config = ModelConfiguration(url: url)
let modelContainer = try ModelContainer(for: [
Item.self
], config)
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(modelContainer)
}
}
The problem here is that I don’t get it to work in the main app struct. When using this the way described in Dive deeper into SwiftData (at 6:58) I only get the error: Cannot use instance member 'url' within property initializer; property initializers run before 'self' is available
PS: There seems to be an issue with this WWDC session method anyway – see this post.
when I run my project, I get this error and the simulator open app and show blank page.
I don't understand because I have to add some stores after I entry the app. but now I cannot entry it.
I used VersionedSchema and SchemaMigrationPlan
According the WWDC23 presentation “What’s new in Core Data”, “Composite attributes may be nested within each other”.
However, in the current macOS and iOS beta builds, only single level composite attributes (without nested composite attributes) are persisted in the SQLite database.
Is this still work in progress?
See example project in FB12552092.
I've been trying to build an example of NSStagedMigrationManager from some Core Data migration tests to replace a custom migration manager solution I'd constructed, without much success.
The Core Data model has seven model versions. Most support lightweight migration, but two of the migrations in the middle of the sequence used NSMappingModel.
In the first beta, just attempting to construct an NSStagedMigrationManager from the series of stages failed with an unrecognized selector. That no longer happens in b4, but I now get an error that "Duplicate version checksums across stages detected."
If I restrict myself to just the first three versions of the model (that only require lightweight migration), I can build the migration manager. But if I attempt to use it to migrate a persistent store, it fails somewhere in NSPersistentStoreCoordinator with a nilError.
The documentation is almost nonexistent for this process, and the WWDC session that introduced it isn't much more than a breezy overview. So maybe I'm holding it wrong?
(And, yes: FB12339663)
In the application iCloud integration but in the container, it displays the name of the bundle that owns the container.
Configuration iCloud capability from developer account
Enable iCloud capability from Xcode
Added keys and value into info.plist file as below
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.example.applepaydemo</key>
<dict>
<key>NSUbiquitousContainerName</key>
<string>Apple Demo</string>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
Issue: It displays applepaydemo name of container not Apple Demo in iCloud Manage Account Storage
It's been frustrating to solve this error. My iOS device and Xcode are fully updated. I can easily run app on simulator, but issue happens on my iPhone.
dyld[23479]: Symbol not found: _$s9SwiftData12ModelContextC6insert6objectyx_tAA010PersistentC0RzlFTj
Referenced from: <6FC773BB-E68B-35A9-B334-3FFC8B951A4E> Expected in: /System/Library/Frameworks/SwiftData.framework/SwiftData
.modelContainer(for: MyMode.self, isUndoEnabled: true)
This may work for single model containers, but I have a number of models. I don't see how to enable undo for multiple model containers.
In Core Data, I can merge two predicates (perform an OR operation) using the following code:
let compound3 = NSCompoundPredicate(type: .or, subpredicates: [compound1, compound2])
How can I achieve a similar operation in SwiftData with new Predicate?
I have created an actor for the ModelContainer, in order to perform a data load when starting the app in the background. For this I have conformed to the ModelActor protocol and created the necessary elements, even preparing for test data.
Then I create a function of type async throws to perform the database loading processes and everything works fine, in that the data is loaded and when loaded it is displayed reactively.
actor Container: ModelActor {
nonisolated let modelContainer: ModelContainer
nonisolated let modelExecutor: ModelExecutor
static let modelContainer: ModelContainer = {
do {
return try ModelContainer(for: Empleados.self)
} catch {
fatalError()
}
}()
let context: ModelContext
init(container: ModelContainer = Container.modelContainer) {
self.modelContainer = container
let context = ModelContext(modelContainer)
self.modelExecutor = DefaultSerialModelExecutor(modelContext: context)
self.context = context
Task {
do {
try await loadData()
} catch {
print("Error en la carga \(error)")
}
}
}
}
The problem is that, in spite of doing the load inside a Task and that there is no problem, when starting the app it stops responding the UI while loading to the user interactions. Which gives me to understand that actually the task that should be in a background thread is running somehow over the MainActor.
As I have my own API that will provide the information to my app and refresh it at each startup or even send them in Batch when the internet connection is lost and comes back, I don't want the user to be continuously noticing that the app stops because it is performing a heavy process that is not really running in the background.
Tested and compiled on Xcode 15 beta 7.
I made a Feedback for this: FB13038621.
Thanks
Julio César
When I reference the ID of a SwiftData model entity (aka PersistentIdentifier) anywhere as a type (like @State var selection: Set<SomeEntity.ID> = Set<SomeEntity.ID>()) I now get the following error:
'ID' is inaccessible due to '@_spi' protection level
This never was a problem with CoreData and also not with SwiftData until the Xcode 15 RC.
Does anybody know, if this is a bug or intended behaviour?
is anyone facing the error, "The current model reference and the next model reference cannot be equal", when using SwiftData with migration and iCloud/CloudKit integration?
I'm encountering an issue encoding/decoding a custom struct from SwiftData. As it's all happening behind the generated code of SwiftData and a decoder, I'm not really sure what's going on.
I have a custom type defined kind of like this:
public struct Group<Key: Hashable, Element: Hashable> {
private var elementGroups: [Element: Key]
private var groupedElements: [Key: [Element]]
}
In short, it allows multiple elements (usually a string), to be grouped, referenced by some key.
I have Codable conformance to this object, so I can encode and decode it. For simplicity, the elementGroups is encoded/decoded, and the groupedElements is rebuilt when decoding. My implementation is similar to this:
extension Group: Codable where Key: Codable, Element: Codable {
private enum Keys: CodingKey {
case groups
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
let decoded = try container.decode([Element: Key].self, forKey: .groups)
// Enumerate over the element groups, and populate the list of elements.
//
var elements: [Key: [Element]] = [:]
for group in decoded {
elements[group.value] = (elements[group.value] ?? []) + [group.key]
}
elementGroups = decoded
groupedElements = elements
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Keys.self)
try container.encode(elementGroups, forKey: .groups)
}
}
This works fine when encoding and decoding to JSON, but when I attempt to use this structure as a value within a SwiftData model, then decoding the type crashes my application.
@Model
final class MyModel {
var id: UUID = UUID()
var groups: Group<UUID, String> = Group<UUID, String>()
init(id: UUID) {
self.id = id
}
}
When something attempts to decode the groups property on the MyModel object, it crashes with the following error:
Could not cast value of type 'Swift.Optional<Any>' (0x1ed7d0290) to 'Swift.Dictionary<Swift.String, Foundation.UUID>' (0x121fa9448).
I would guess that there is a nil value stored for groups in SwiftData, and attempting to decode it to a Group<UUID, String> type is failing. It's odd that it doesn't throw an exception though, and hard crashes. Also, I'm not sure why it's optional, as a value is being written out.
Does anyone have any ideas?
I'm using two loops, one nested in another to create and assign a subclass to the parent class. I'm using x for one loop and y for the other loop. Print statement shows the loop variables being updated correctly, however the subclass has both variables as the same value. I'm not sure if the value is being stored as the same value or being displayed as the same value. I've checked both creation and display code. I can't find the error.
var body: some View {
NavigationSplitView {
List {
ForEach(routes) { item in
NavigationLink {
Text("Route: \(item.route_name) \nSeason: \(item.route_season)\nCoordinates: \(item.route_coordinates)\nProcessed: \(String(item.route_processed))\nNumber of baseboards: \(item.route_baseboards.count)")
} label: {
Text(item.route_name)
} //end route nav link
} //end route for each
.onDelete(perform: deleteItems) //end route nav
ForEach(baseboards) { item in
NavigationLink {
//if let test = item.baseboard_heightPoints.count {
Text("Grid Size: \(String(item.baseboard_grid_size))\nRow:\(String(item.baseboard_row))\nColumn:\(String(item.baseboard_column))\nHeight Point Count:")//\(String(item.baseboard_heightPoints.flatMap { $0 } .count))") //fix count later
//Text("Test")
// Text("test2")
// } else {
// Text("Grid Size: \(String(item.baseboard_grid_size))\nRow:\(String(item.baseboard_row))\nColumn:\(String(item.baseboard_column))\nHeight Point Count: 0")
// }
} label: {
Text(String(item.baseboard_grid_size))
}}
.onDelete(perform: deleteItemsBaseboards) //end baseboard route nav
// ForEach(heightPoints) { item in NavigationLink {
// Text("Row:\(String(item.hp_row))\nColumn:\(String(item.hp_column))\nElevation:\(String(item.hp_elevation))")
// } label: {
// Text("Row:\(String(item.hp_row))\nColumn:\(String(item.hp_column))")
// }}
//.onDelete(perform: deleteItemsBaseboards)
}
.toolbar {
ToolbarItem {
Button(action: addItem) {
Label("Add Route", systemImage: "plus")
}
//Button(action: addItem) { //not showing up
// Label("Add Baseboard", systemImage: "arrow.up")
//}
}
} //end toolbar
} detail: {
Text("Select a route")
} //end NavigationSplitView
} //end view body
private func addItem() {
/*withAnimation { */
let newItem = Route(
route_name: "test route " + UUID().uuidString,
route_season: "summer",
route_processed: false,
route_coordinates: "Somewhere",
route_region: "US",
route_baseboards: [])
modelContext.insert(newItem) //end add route
//add baseboards to each route
let bb_StartCol = 0
let bb_EndCol = 3
let bb_StartRow = 0
let bb_EndRow = 3
//let grid5m = 148
//let grid10m = 76
//let gridHD = 5760
var bb_grid_size = 5760
let bb_sectionsVerticle = 180
let bb_sectionsHorizonal = 360
var sectionData: Data
var dataInputArray: [UInt8] = [0x03, 0x00, 0x00, 0x00, 0x00, 0x82, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x20, 0xF8, 0xFF]
sectionData = Data(withUnsafeBytes(of: dataInputArray, Array.init))
for x in bb_StartCol...bb_EndCol { //columns
for y in bb_StartRow...bb_EndRow { //rows
print(x,y)
if bb_grid_size < 1000 {
let newItem2 = Baseboard(
baseboard_column: Int16(x),
baseboard_row: Int16(y),
texture: "Grid",
baseboard_processed: false,
baseboard_grid_size: Int16(bb_grid_size),//(x+2)+(y+2),
baseboard_heightPoints: Array(repeating: Array(repeating: 0, count: bb_grid_size), count: bb_grid_size),
baseboard_HPSection: [],
baseboard_route: newItem
//baseboard_hps: []
)
modelContext.insert(newItem2) //insert baseboard for each run
} else {
let newItem2 = Baseboard(
baseboard_column: Int16(x),
baseboard_row: Int16(y),
texture: "Grid",
baseboard_processed: false,
baseboard_grid_size: Int16(bb_grid_size),//(x+2)+(y+2),
baseboard_heightPoints: [],
baseboard_HPSection: Array(repeating: Array(repeating: sectionData, count: bb_sectionsVerticle), count: bb_sectionsHorizonal),
baseboard_route: newItem
//baseboard_hps: []
)
modelContext.insert(newItem2) //insert baseboard for each run
}
//print(x,y)
} //end y for loop - baseboards
} //end x for loop - baseboards
// } // end animation of adding new items
} // end function add items
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(routes[index])
}
}
}
private func deleteItemsBaseboards(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(baseboards[index])
}
}
}
//private func deleteItemsHeightPoints(offsets: IndexSet) {
// withAnimation {
// for index in offsets {
// modelContext.delete(heightPoints[index])
// }
// }
// }
}
#Preview {
ContentView()
.modelContainer(for: Route.self, inMemory: false)
}
I had a simple class called Entry with one field (timestamp: Date) and I tried to add a new field without having to uninstall the app , so I would not crash.
I tried implementing the MigrationPlan stuff but I do not know if I have to manually add a value to the new field or how exactly it works.
Here is my code.
When I use the EntrySchemaV0 it works fine but if I try to use the V1 it crashes. (could not fetch ModelContainer)
I also tried it with a custom migration stage. Crashes with error (SwiftData/BackingData.swift:432: Fatal error: Expected only Arrays for Relationships - String)
import Foundation
import SwiftData
enum EntrySchemaV0: VersionedSchema {
static var versionIdentifier = Schema.Version(0, 1, 0)
static var models: [any PersistentModel.Type] {
[Entry.self]
}
@Model
final class Entry {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
}
enum EntrySchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(0, 2, 0)
static var models: [any PersistentModel.Type] {
[Entry.self]
}
@Model
final class Entry {
var name: String
var timestamp: Date
init(name: String = "", timestamp: Date = .now) {
self.name = name
self.timestamp = timestamp
}
}
}
enum EntryMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[EntrySchemaV0.self, EntrySchemaV1.self]
}
static var stages: [MigrationStage] {
[migrateV0toV1]
}
static let migrateV0toV1 = MigrationStage.lightweight(
fromVersion: EntrySchemaV0.self,
toVersion: EntrySchemaV1.self
)
}
I enabled Advanced Data Protection for my developer account, and this (understandably) broke access to my private records in CloudKit Console.
I disabled Advanced Data Protection but CloudKit Console still cannot connect. In the database popup the "Click here to retry..." option always fails silently.
Does anyone know a workaround?
So I am trying to sync only some of my Models with iCloud and others kept locally in the default.store. I am having a world of issues so before I start looking for the needle in my haystack. I would like to ask this forum, is my approach one that should work as desired or is my code obviously why things are not working? All my Models have default values and relationships where needed are optionals. iCloud is working but my issue arises when i try to exclude some models. So I want to sync "modelsForCloudSyncing" but not "modelNotForCloudSyncing" In advance thank you
var avoidCloudSyncModelContainer : ModelContainer = {
let modelNotForCloudSyncing = Schema([NoCloudSyncModel.self])
let modelConfigForNoCloudSync = ModelConfiguration(schema: modelNotForCloudSyncing, cloudKitDatabase: .none)
let modelsForCloudSyncing = Schema([CloudSyncModelA.self, CloudSyncModelB.self, CloudSyncModelC.self])
let modelConfigForCloudSync = ModelConfiguration(schema: modelsForCloudSyncing, cloudKitDatabase: .automatic)
do {
return try ModelContainer(for: NoCloudSyncModel.self, CloudSyncModelA.self, CloudSyncModelB.self, CloudSyncModelC.self, configurations: modelConfigForNoCloudSync, modelConfigForCloudSync)
}
catch { fatalError("Could not create ModelContainer: \(error)") }
}()
...
{
ContentView()
}.modelContainer(avoidCloudSyncModelContainer)
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.
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 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?