I’m trying to build a CRUD app using SwiftData, @Query model and multidatepicker.
The data from a multidatepicker is stored or persists in SwiftData as Set = [].
My current dilemma is how to use SwiftData and @Query model Predicate to find all records on the current date.
I can’t find any SwiftData documentation or examples @Query using Set = [].
My CRUD app should retrieve all records for the current date. Unfortunately, I don’t know the correct @Query model syntax for Set = [].
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
Activity
I'm looking for guidance how to mitigate this crash. It seems super deep inside Core Data' FRC fetchedObjects management.
In my code, it's initiated by this
viewContext.perform {
[unowned self] in
self.viewContext.mergeChanges(fromContextDidSave: notification)
}
which is directly followed by the stack trace below.
Basically merging data from .NSManagedObjectContextDidSave notification from another NSManagedObjectContext. Nothing special, it works great for years, apart from these rare occurrences.
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Reason: -[__NSCFArray objectAtIndex:]: index (235) beyond bounds (234)
Termination Reason: SIGNAL 6 Abort trap: 6
Triggered by Thread: 0
Last Exception Backtrace:
0 CoreFoundation 0x199e947cc __exceptionPreprocess + 164 (NSException.m:249)
1 libobjc.A.dylib 0x1971672e4 objc_exception_throw + 88 (objc-exception.mm:356)
2 CoreFoundation 0x199fc4258 _NSArrayRaiseBoundException + 368 (NSCFArray.m:22)
3 CoreFoundation 0x199e288a4 -[__NSCFArray objectAtIndex:] + 200 (NSCFArray.m:42)
4 CoreData 0x1a1e17338 -[_PFMutableProxyArray objectAtIndex:] + 40 (_PFArray.m:1860)
5 CoreData 0x1a1e1673c -[NSFetchedResultsController _updateFetchedObjectsWithInsertChange:] + 380 (NSFetchedResultsController.m:1582)
6 CoreData 0x1a1e1426c __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 2240 (NSFetchedResultsController.m:2171)
7 CoreData 0x1a1dcdf80 developerSubmittedBlockToNSManagedObjectContextPerform + 156 (NSManagedObjectContext.m:4002)
8 CoreData 0x1a1e41a44 -[NSManagedObjectContext performBlockAndWait:] + 216 (NSManagedObjectContext.m:4113)
9 CoreData 0x1a1e41034 -[NSFetchedResultsController _core_managedObjectContextDidChange:] + 124 (NSFetchedResultsController.m:2379)
10 CoreFoundation 0x199e632f4 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 148 (CFNotificationCenter.c:701)
11 CoreFoundation 0x199e63210 ___CFXRegistrationPost_block_invoke + 88 (CFNotificationCenter.c:194)
12 CoreFoundation 0x199e63158 _CFXRegistrationPost + 436 (CFNotificationCenter.c:222)
13 CoreFoundation 0x199e6170c _CFXNotificationPost + 728 (CFNotificationCenter.c:1248)
14 Foundation 0x198a84ea4 -[NSNotificationCenter postNotificationName:object:userInfo:] + 92 (NSNotification.m:531)
15 CoreData 0x1a1e11650 -[NSManagedObjectContext _createAndPostChangeNotification:deletions:updates:refreshes:deferrals:wasMerge:] + 1736 (NSManagedObjectContext.m:8098)
16 CoreData 0x1a1e10e0c -[NSManagedObjectContext _postRefreshedObjectsNotificationAndClearList] + 164 (NSManagedObjectContext.m:7631)
17 CoreData 0x1a1e0fad8 -[NSManagedObjectContext _processRecentChanges:] + 100 (NSManagedObjectContext.m:7714)
18 CoreData 0x1a1e3563c -[NSManagedObjectContext _coreMergeChangesFromDidSaveDictionary:usingObjectIDs:withClientQueryGeneration:] + 3436 (NSManagedObjectContext.m:3723)
19 CoreData 0x1a1e34350 __116+[NSManagedObjectContext(_NSCoreDataSPI) _mergeChangesFromRemoteContextSave:intoContexts:withClientQueryGeneration:]_block_invoke_4 + 76 (NSManagedObjectContext.m:9531)
20 CoreData 0x1a1dcdf80 developerSubmittedBlockToNSManagedObjectContextPerform + 156 (NSManagedObjectContext.m:4002)
21 CoreData 0x1a1e41a44 -[NSManagedObjectContext performBlockAndWait:] + 216 (NSManagedObjectContext.m:4113)
22 CoreData 0x1a1e39880 +[NSManagedObjectContext _mergeChangesFromRemoteContextSave:intoContexts:withClientQueryGeneration:] + 2372 (NSManagedObjectContext.m:9537)
23 CoreData 0x1a1e344a0 -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] + 292 (NSManagedObjectContext.m:0)
Topic:
App & System Services
SubTopic:
iCloud & Data
Hello everyone,
I'm trying to adopt the new Staged Migrations for Core Data and I keep running into an error that I haven't been able to resolve.
The error messages are as follows:
warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass 'Movie' so +entity is unable to disambiguate.
warning: 'Movie' (0x60000350d6b0) from NSManagedObjectModel (0x60000213a8a0) claims 'Movie'.
error: +[Movie entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
This happens for all of my entities when they are added/fetched. Movie is an abstract entity subclass, and it has the error error: +[Movie entity] Failed to find which is unique to the subclass entities, but this occurs for all entities.
The NSPersistentContainer is loaded only once, and I set the following option after it's loaded:
storeDescription.setOption(
[stages],
forKey: NSPersistentStoreStagedMigrationManagerOptionKey
)
The warnings and errors only appear after I fetch or save to context. It happens regardless of whether the database was migrated or not. In my test project, using the generic NSManagedObject with NSEntityDescription.insertNewObject(forEntityName: "MyEntity", into: context) does not cause the issue. However, using the generic NSManagedObject is not a viable option for my app.
Setting the module to "Current Project Module" doesn't change anything, except that it now prints "claims 'MyModule.Show'" in the warnings. I have verified that there are no other entities with the same name or renameIdentifier.
Has anyone else encountered this issue, or can offer any suggestions on how to resolve it?
Thanks in advance for any help!
I get this message when trying to save my Models.
CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLSaveChangesRequestContext: 0x303034540> , I/O error for database at /var/mobile/Containers/Data/Application/726ECA8C-6C67-4BFE-89E7-AFD8A83CAA5D/Library/Application Support/default.store. SQLite error code:1, 'no such table: ZCALENDARMODEL' with userInfo of {
NSFilePath = "/var/mobile/Containers/Data/Application/726ECA8C-6C67-4BFE-89E7-AFD8A83CAA5D/Library/Application Support/default.store";
NSSQLiteErrorDomain = 1;
}
SwiftData.DefaultStore save failed with error: Error Domain=NSCocoaErrorDomain Code=256 "The file “default.store” couldn’t be opened." UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/726ECA8C-6C67-4BFE-89E7-AFD8A83CAA5D/Library/Application Support/default.store, NSSQLiteErrorDomain=1}
The App has Recipes and Calendars and the user can select a Recipe for each Calendar day. The recipe should not be referenced, it should be saved by SwiftData along with the Calendar.
import SwiftUI
import SwiftData
enum CalendarSource: String, Codable {
case created
case imported
}
@Model
class CalendarModel: Identifiable, Codable {
var id: UUID = UUID()
var name: String
var startDate: Date
var endDate: Date
var recipes: [String: RecipeData] = [:]
var thumbnailData: Data?
var source: CalendarSource?
// Computed Properties
var daysBetween: Int {
let days = Calendar.current.dateComponents([.day], from: startDate.midnight, to: endDate.midnight).day ?? 0
return days + 1
}
var allDates: [Date] {
startDate.midnight.allDates(upTo: endDate.midnight)
}
var thumbnailImage: Image? {
if let data = thumbnailData, let uiImage = UIImage(data: data) {
return Image(uiImage: uiImage)
} else {
return nil
}
}
// Initializer
init(name: String, startDate: Date, endDate: Date, thumbnailData: Data? = nil, source: CalendarSource? = .created) {
self.name = name
self.startDate = startDate
self.endDate = endDate
self.thumbnailData = thumbnailData
self.source = source
}
// Convenience initializer to create a copy of an existing calendar
static func copy(from calendar: CalendarModel) -> CalendarModel {
let copiedCalendar = CalendarModel(
name: calendar.name,
startDate: calendar.startDate,
endDate: calendar.endDate,
thumbnailData: calendar.thumbnailData,
source: calendar.source
)
// Copy recipes
copiedCalendar.recipes = calendar.recipes.mapValues { $0 }
return copiedCalendar
}
// Codable Conformance
private enum CodingKeys: String, CodingKey {
case id, name, startDate, endDate, recipes, thumbnailData, source
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
startDate = try container.decode(Date.self, forKey: .startDate)
endDate = try container.decode(Date.self, forKey: .endDate)
recipes = try container.decode([String: RecipeData].self, forKey: .recipes)
thumbnailData = try container.decodeIfPresent(Data.self, forKey: .thumbnailData)
source = try container.decodeIfPresent(CalendarSource.self, forKey: .source)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(startDate, forKey: .startDate)
try container.encode(endDate, forKey: .endDate)
try container.encode(recipes, forKey: .recipes)
try container.encode(thumbnailData, forKey: .thumbnailData)
try container.encode(source, forKey: .source)
}
}
import SwiftUI
struct RecipeData: Codable, Identifiable {
var id: UUID = UUID()
var name: String
var ingredients: String
var steps: String
var thumbnailData: Data?
// Computed property to convert thumbnail data to a SwiftUI Image
var thumbnailImage: Image? {
if let data = thumbnailData, let uiImage = UIImage(data: data) {
return Image(uiImage: uiImage)
} else {
return nil // No image
}
}
init(recipe: RecipeModel) {
self.name = recipe.name
self.ingredients = recipe.ingredients
self.steps = recipe.steps
self.thumbnailData = recipe.thumbnailData
}
}
import SwiftUI
import SwiftData
@Model
class RecipeModel: Identifiable, Codable {
var id: UUID = UUID()
var name: String
var ingredients: String
var steps: String
var thumbnailData: Data? // Store the image data for the thumbnail
static let fallbackSymbols = ["book.pages.fill", "carrot.fill", "fork.knife", "stove.fill"]
// Computed property to convert thumbnail data to a SwiftUI Image
var thumbnailImage: Image? {
if let data = thumbnailData, let uiImage = UIImage(data: data) {
return Image(uiImage: uiImage)
} else {
return nil // No image
}
}
// MARK: - Initializer
init(name: String, ingredients: String = "", steps: String = "", thumbnailData: Data? = nil) {
self.name = name
self.ingredients = ingredients
self.steps = steps
self.thumbnailData = thumbnailData
}
// MARK: - Copy Function
func copy() -> RecipeModel {
RecipeModel(
name: self.name,
ingredients: self.ingredients,
steps: self.steps,
thumbnailData: self.thumbnailData
)
}
// MARK: - Codable Conformance
private enum CodingKeys: String, CodingKey {
case id, name, ingredients, steps, thumbnailData
}
required init(from decoder: Decoder) throws {
...
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(ingredients, forKey: .ingredients)
try container.encode(steps, forKey: .steps)
try container.encode(thumbnailData, forKey: .thumbnailData)
}
}
Testing Environment: iOS 18.4.1 / macOS 15.4.1
I am working on an iOS project that aims to utilize the user's iCloud Drive documents directory to save a specific directory-based file structure. Essentially, the app would create a root directory where the user chooses in iCloud Drive, then it would populate user generated files in various levels of nested directories.
I have been attempting to use NSMetadataQuery with various predicates and search scopes but haven't been able to get it to directly monitor changes to files or directories that are not in the root directory.
Instead, it only monitors files or directories in the root directory, and any changes in a subdirectory are considered an update to the direct children of the root directory.
Example
iCloud Drive Documents (Not app's ubiquity container)
User Created Root Directory (Being monitored)
File A
Directory A
File B
An insertion or deletion within Directory A would only return a notification with userInfo containing data for NSMetadataQueryUpdateChangedItemsKey relating to Directory A, and not the file or directory itself that was inserted or deleted. (Query results array also only contain the direct children.)
I have tried all combinations of these search scopes and predicates with no luck:
query.searchScopes = [
rootDirectoryURL,
NSMetadataQueryUbiquitousDocumentsScope,
NSMetadataQueryAccessibleUbiquitousExternalDocumentsScope,
]
NSPredicate(value: true)
NSPredicate(format: "%K LIKE '*.md'", NSMetadataItemFSNameKey)
NSPredicate(format: "%K BEGINSWITH %@", NSMetadataItemPathKey, url.path(percentEncoded: false))
I do see these warnings in the console upon starting my query:
[CRIT] UNREACHABLE: failed to get container URL for com.apple.CloudDocs
[ERROR] couldn't fetch remote operation IDs: NSError: Cocoa 257 "The file couldn’t be opened because you don’t have permission to view it."
"Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
But I am not sure what to make of that, since it does act normally for finding updates in the root directory.
Hopefully this isn't a limitation of the API, as the only alternative I could think of would be to have multiple queries running for each nested directory that I needed updates for.
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
Files and Storage
iCloud Drive
Foundation
I have two contexts, MAIN and VIEW. I construct an object in MAIN and it appears in VIEW (which I use to display it in the UI). Then I delete the object in MAIN. Because the UI holds a reference to the object in VIEW, VIEW records it as a pending delete (Problem 1). I don't understand why it does this nor can I find this behaviour documented. Docs for deletedObjects say "objects that will be removed from their persistent store during the next save". This has already happened!
(Problem 2) Then I rollback the VIEW context, and the object is resurrected. awakeFromInsert is called again. While the object (correctly) does not appear in a freshly executed fetch request, it does appear in the @FetchRequest of the SwiftUI View which is now displaying stale data. I cannot figure out how to get SwiftUI to execute the fetch request again (I know I can force regeneration of the UI, but would like to avoid this).
This is self-contained demonstration of the problem that can be run in a Playground. Press Create, then Delete (note console output), then Rollback (note console output, and that element count changes from 0 to 1 in the UI)
import CoreData
import SwiftUI
@objc(TestEntity)
class TestEntity : NSManagedObject, Identifiable{
@NSManaged var id : UUID?
override func awakeFromInsert() {
print("Awake from insert")
if id == nil {
// Avoid resetting ID when we resurrect the phantom delete
self.id = UUID()
}
super.awakeFromInsert()
}
class func add(in context: NSManagedObjectContext) -> UUID {
let id = UUID()
context.performAndWait {
let mo = TestEntity(context: context)
mo.id = id
}
return id
}
class func fetch(in context: NSManagedObjectContext) -> [TestEntity] {
let fr = TestEntity.fetchRequest()
return try! context.fetch(fr) as! [TestEntity]
}
}
class CoreDataStack {
// Main is attached to the store
var main : NSManagedObjectContext!
// View is a child context of main and used to display the UI
var view : NSManagedObjectContext!
// Set up a simple entity with an ID attribute
func getEntities() -> [NSEntityDescription] {
let testEntity = NSEntityDescription()
testEntity.managedObjectClassName = "TestEntity"
testEntity.name = "TestEntity"
let idAttribute = NSAttributeDescription()
idAttribute.name = "id"
idAttribute.type = .uuid
testEntity.properties.append(idAttribute)
return [testEntity]
}
init() {
let model = NSManagedObjectModel()
model.entities = getEntities()
let container = NSPersistentContainer(name: "TestModel", managedObjectModel: model)
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { desc, error in
if error != nil {
fatalError("Failed to set up coredata")
}
}
main = container.viewContext
view = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
view.automaticallyMergesChangesFromParent = true
view.parent = main
}
func create() {
let entityId = TestEntity.add(in: main)
main.performAndWait {
try! main.save()
}
}
func delete() {
main.performAndWait {
if let mo = TestEntity.fetch(in: main).first {
main.delete(mo)
try! main.save()
}
}
self.view.perform {
// We only find that we have a pending delete here if we hold a reference to the object, e.g. in the UI via @FetchRequest
if(self.view.deletedObjects.count != 0) {
print("!!! view has a pending delete, even though main has saved the delete !!!")
}
}
}
func rollback() {
self.view.perform {
self.view.rollback()
// PROBLEM We now have a resurrected object. Note that awakeFromInsert
// was called again.
}
}
}
import SwiftUI
import PlaygroundSupport
let stack = CoreDataStack()
struct ContentView: View {
@FetchRequest(sortDescriptors: []) private var entities: FetchedResults<TestEntity>
@State var renderID = UUID()
var body: some View {
VStack {
Text("\(entities.count) elements")
Button("Create") {
stack.create()
}
Button("Delete") {
stack.delete()
}
Button("Rollback") {
stack.rollback()
// PROBLEM After rollback we get the element displaying in
// the UI again, even though it isn't present in a freshly
// executed fetch request.
// The @FetchRequest is picking up the resurrected TestEntity in view
// But not actually issuing a fetch.
self.renderID = UUID()
entities.nsPredicate
}
}.id(renderID)
}
}
//stack.execute()
let view = ContentView()
.environment(\.managedObjectContext, stack.view)
PlaygroundPage.current.setLiveView(view)
why not working?
Topic:
App & System Services
SubTopic:
iCloud & Data
I have an app that uses Core Data. I'm switching to SwiftData but it looks like the sqlite files are stored in separate places in the application file directory so my SwiftData files aren't reading the CoreData store. I'm not sure why it's not reading from the same location. Is there something I'm missing? Here's an example of the paths that I see when I write information to the debug console:
SwiftData Path: file:///Users/dougthiele/Library/Developer/CoreSimulator/Devices/52CE32F8-F6A9-4825-8027-994DBE47173C/data/Containers/Data/Application/63E9B61D-64B8-4D2D-A02C-3C306688F354/Documents/[Data File Name].sqlite
Core Data Path:
file:///Users/dougthiele/Library/Developer/CoreSimulator/Devices/52CE32F8-F6A9-4825-8027-994DBE47173C/data/Containers/Data/Application/96A5961B-54DD-43A9-A4C3-661B439D91AE/Documents/[Data File Name].sqlite
Topic:
App & System Services
SubTopic:
iCloud & Data
So i created an App and for some time it was working fine. The app has features to show pdf to users without logging in. I needed to upload all data to cloudkit on public database.
I was not having knowledge that there are 2 mode being a noob in coding so after i saved all records in development mode in cloudkit when i published my app, i was not able to see them (Reason because live mode works in Production mode).
So i need help now to transfer data from development mode to production mode or any app or code that can help me upload all data in production mode.
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
CloudKit
CloudKit Dashboard
CloudKit Console
I am currently developing an iOS app with a new feature that utilizes Quick Start for data migration between devices. We are testing this in a test environment using an app distributed via TestFlight.
However, we are encountering an issue where the app installed on the pre-migration device (distributed via TestFlight) does not transfer to the post-migration device. Could this issue be related to the fact that the app was distributed via TestFlight? Is there any restriction where only apps released via the App Store can be migrated using Quick Start?
We would appreciate it if you could provide some insights into the cause of this issue and any alternative testing methods.
For a CRM application, I want users to be able to switch between accounts and have their saved contacts stored locally. Whenever a user logs in, the app should fetch data from their specific database location.
What’s the best practice to achieve this?
Should I create a separate database for each user?
Should I store all the data in one database and filter it by user?
Or is there a better approach I should consider?
Hello. I am re-writing our way of storing data into Core Data in our app, so it can be done concurrently.
The solution I opted for is to have a singleton actor that takes an API model, and maps it to a Core Data object and saves it.
For example, to store an API order model, I have something like this:
func store(
order apiOrder: APIOrder,
currentContext: NSManagedObjectContext?
) -> NSManagedObjectID? {
let context = currentContext ?? self.persistentContainer.newBackgroundContext()
// …
}
In the arguments, there is a context you can pass, in case you need to create additional models and relate them to each other. I am not sure this is how you're supposed to do it, but it seemed to work.
From what I've understood of Core Data and using multiple contexts, the appropriate way use them is with context.perform or context.performAndWait.
However, since my storage helper is an actor, @globalActor actor Storage2 { … }, my storage's methods are actor-isolated.
This gives me warnings / errors in Swift 6 when I try to pass the context for to another of my actor's methods.
let context = …
return context.performAndWait {
// …
if let apiBooking = apiOrder.booking {
self.store(booking: apiBooking, context: context)
/* causes warning:
Sending 'context' risks causing data races; this is an error in the Swift 6 language mode
'self'-isolated 'context' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
Access can happen concurrently
*/
}
// …
}
From what I understand this is because my methods are actor-isolated, but the closure of performAndWait does not execute in a thread safe environment.
With all this, what are my options? I've extracted the store(departure:context:) into its own method to avoid duplicated code, but since I can't call it from within performAndWait I am not sure what to do.
Can I ditch the performAndWait? Removing that makes the warning "go away", but I don't feel confident enough with Core Data to know the answer.
I would love to get any feedback on this, hoping to learn!
Hi there, I got two models here:
Two Models, with Many-To-Many Relationship
@Model
final class PresetParams: Identifiable {
@Attribute(.unique) var id: UUID = UUID()
var positionX: Float = 0.0
var positionY: Float = 0.0
var positionZ: Float = 0.0
var volume: Float = 1.0
@Relationship(deleteRule: .nullify, inverse: \Preset.presetAudioParams)
var preset = [Preset]()
init(position: SIMD3<Float>, volume: Float) {
self.positionX = position.x
self.positionY = position.y
self.positionZ = position.z
self.volume = volume
self.preset = []
}
var position: SIMD3<Float> {
get {
return SIMD3<Float>(x: positionX, y: positionY, z: positionZ)
}
set {
positionX = newValue.x
positionY = newValue.y
positionZ = newValue.z
}
}
}
@Model
final class Preset: Identifiable {
@Attribute(.unique) var id: UUID = UUID()
var presetName: String
var presetDesc: String?
var presetAudioParams = [PresetParams]() // Many-To-Many Relationship.
init(presetName: String, presetDesc: String? = nil) {
self.presetName = presetName
self.presetDesc = presetDesc
self.presetAudioParams = []
}
}
To be honest, I don't fully understand how the @Relationship thing works properly in a Many-To-Many relationship situation. Some tutorials suggest that it's required on the "One" side of an One-To-Many Relationship, while the "Many" side doesn't need it.
And then there is an ObservableObject called "ModelActors" to manage all ModelActors, ModelContainer, etc.
ModelActors, ModelContainer...
class ModelActors: ObservableObject {
static let shared: ModelActors = ModelActors()
let sharedModelContainer: ModelContainer
private init() {
var schema = Schema([
// ...
Preset.self,
PresetParams.self,
// ...
])
do {
sharedModelContainer = try ModelContainer(for: schema, migrationPlan: MigrationPlan.self)
} catch {
fatalError("Could not create ModelContainer: \(error.localizedDescription)")
}
}
}
And there is a migrationPlan:
MigrationPlan
// MARK: V102
// typealias ...
// MARK: V101
typealias Preset = AppSchemaV101.Preset
typealias PresetParams = AppSchemaV101.PresetParams
// MARK: V100
// typealias ...
enum MigrationPlan: SchemaMigrationPlan {
static var schemas: [VersionedSchema.Type] {
[
AppSchemaV100.self,
AppSchemaV101.self,
AppSchemaV102.self,
]
}
static var stages: [MigrationStage] {
[AppMigrateV100toV101, AppMigrateV101toV102]
}
static let AppMigrateV100toV101 = MigrationStage.lightweight(fromVersion: AppSchemaV100.self, toVersion: AppSchemaV101.self)
static let AppMigrateV101toV102 = MigrationStage.lightweight(fromVersion: AppSchemaV101.self, toVersion: AppSchemaV102.self)
}
// MARK: Here is the AppSchemaV101
enum AppSchemaV101: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(1, 0, 1)
static var models: [any PersistentModel.Type] {
return [ // ...
Preset.self,
PresetParams.self
]
}
}
Fails on iOS 18.3.x: "Failed to fulfill link PendingRelationshipLink"
So I expected the SwiftData subsystem to work correctly with version control. A good news is that on iOS 18.1 it does work. But it fails on iOS 18.3.x with a fatal Error:
"SwiftData/SchemaCoreData.swift:581: Fatal error: Failed to fulfill link PendingRelationshipLink(relationshipDescription: (<NSRelationshipDescription: 0x30377fe80>), name preset, isOptional 0, isTransient 0, entity PresetParams, renamingIdentifier preset, validation predicates (), warnings (), versionHashModifier (null)userInfo {}, destination entity Preset, inverseRelationship (null), minCount 0, maxCount 0, isOrdered 0, deleteRule 1, destinationEntityName: "Preset", inverseRelationshipName: Optional("presetAudioParams")), couldn't find inverse relationship 'Preset.presetAudioParams' in model"
Fails on iOS 17.5: Another Error
I tested it on iOS 17.5 and found another issue: Accessing or mutating the "PresetAudioParams" property causes the SwiftData Macro Codes to crash, affecting both Getter and Setter. It fails with an error:
"EXC_BREAKPOINT (code=1, subcode=0x1cc1698ec)"
Tweaking the @Relationship marker and ModelContainer settings didn't fix the problem.
I really don't understand what kind of cyber crap CloudKit is!
In macOS, CloudKit basically doesn’t work properly
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
CloudKit
CloudKit Dashboard
CloudKit Console
After copying and inserting instances I am getting strange duplicate values in arrays before saving.
My models:
@Model
class Car: Identifiable {
@Attribute(.unique)
var name: String
var carData: CarData
func copy() -> Car {
Car(
name: "temporaryNewName",
carData: carData
)
}
}
@Model
class CarData: Identifiable {
var id: UUID = UUID()
var featuresA: [Feature]
var featuresB: [Feature]
func copy() -> CarData {
CarData(
id: UUID(),
featuresA: featuresA,
featuresB: featuresB
)
}
}
@Model
class Feature: Identifiable {
@Attribute(.unique)
var id: Int
@Attribute(.unique)
var name: String
@Relationship(
deleteRule:.cascade,
inverse: \CarData.featuresA
)
private(set) var carDatasA: [CarData]?
@Relationship(
deleteRule:.cascade,
inverse: \CarData.featuresB
)
private(set) var carDatasB: [CarData]?
}
The Car instances are created and saved to SwiftData, after that in code:
var fetchDescriptor = FetchDescriptor<Car>(
predicate: #Predicate<Car> {
car in
car.name == name
}
)
let cars = try! modelContext.fetch(
fetchDescriptor
)
let car = cars.first!
print("car featuresA:", car.featuresA.map{$0.name}) //prints ["green"] - expected
let newCar = car.copy()
newCar.name = "Another car"
newcar.carData = car.carData.copy()
print("newCar featuresA:", newCar.featuresA.map{$0.name}) //prints ["green"] - expected
modelContext.insert(newCar)
print("newCar featuresA:", newCar.featuresA.map{$0.name}) //prints ["green", "green"] - UNEXPECTED!
/*some code planned here modifying newCar.featuresA, but they are wrong here causing issues,
for example finding first expected green value and removing it will still keep the unexpected duplicate
(unless iterating over all arrays to delete all unexpected duplicates - not optimal and sloooooow).*/
try! modelContext.save()
print("newCar featuresA:", newCar.featuresA.map{$0.name}) //prints ["green"] - self-auto-healed???
Tested on iOS 18.2 simulator and iOS 18.3.1 device. Minimum deployment target: iOS 17.4
The business logic is that new instances need to be created by copying and modifying previously created ones, but I would like to avoid saving before all instances are created, because saving after creating each instance separately takes too much time overall. (In real life scenario there are more than 10K objects with much more properties, updating just ~10 instances with saving takes around 1 minute on iPhone 16 Pro.)
Is this a bug, or how can I modify the code (without workarounds like deleting duplicate values) to not get duplicate values between insert() and save()?
I am trying to implement record sharing in my project, but when I try to copy the link on the UICloudSharingController, the sheet closes and the link doesn't get copied.
My CloudKitManager function:
public func shareTeam(_ team: Team) -> AnyPublisher<CKShare, Error> {
Future { [weak self] promise in
guard let self = self else {
promise(.failure(CloudKitError.unknown))
return
}
let record = team.toCKRecord()
let share = CKShare(rootRecord: record)
share[CKShare.SystemFieldKey.title] = "Join \(team.name)" as CKRecordValue
share.publicPermission = .readWrite
let operation = CKModifyRecordsOperation(recordsToSave: [record, share], recordIDsToDelete: nil)
operation.savePolicy = .ifServerRecordUnchanged
operation.qualityOfService = .userInitiated
operation.modifyRecordsResultBlock = { result in
switch result {
case .success:
promise(.success(share))
case .failure(let error):
promise(.failure(error))
}
}
self.privateDatabase.add(operation)
}
.eraseToAnyPublisher()
}
ViewModel function:
func shareTeam() {
guard let selectedTeam = selectedTeam else { return }
CloudKitManager.shared.shareTeam(selectedTeam)
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
switch completion {
case .finished:
break
case .failure(let error):
self?.didError = true
self?.error = error
}
} receiveValue: { share in
let sharePresenter = SharePresenter(
share: share,
container: CloudKitManager.shared.container,
teamName: selectedTeam.name,
rootRecord: selectedTeam.toCKRecord()
)
sharePresenter.presentShareSheet()
}
.store(in: &cancellables)
}
I am working on a software where we want to add the feature to share the whole database with the other user. Database is iCloud combined with coredata. The other user(s) should be able to edit /delete and even create new objects in the share.
I did this with this code witch directly from sample code
let participants = try await ckConainer.fetchParticipants(matching: [lookupInfo], into: selectedStore)
for participant in participants {
participant.permission = .readWrite
participant.role = .privateUser
share.addParticipant(participant)
}
try await ckConainer.persistUpdatedShare(share, in: selectedStore)
the other user gets invited and I can see this in iCloud database that the other user is invited with status invited.
but the other user never gets a mail or something to accept and join the share. How does the other needs to accept the invitation ?
I have not had any successful Schema Migration with CloudKit so far so I'm trying to do with with just very basic attributes, with multiple Versioned Schemas
This is the code in my App Main
var sharedModelContainer: ModelContainer = {
let schema = Schema(versionedSchema: AppSchemaV4.self)
do {
return try ModelContainer(
for: schema,
migrationPlan: AppMigrationPlan.self,
configurations: ModelConfiguration(cloudKitDatabase: .automatic))
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ItemListView()
}
.modelContainer(sharedModelContainer)
}
And this is the code for my MigrationPlan and VersionedSchemas.
typealias Item = AppSchemaV4.Item3
enum AppMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[AppSchemaV1.self, AppSchemaV2.self, AppSchemaV3.self, AppSchemaV4.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3, migrateV3toV4]
}
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: AppSchemaV1.self,
toVersion: AppSchemaV2.self
)
static let migrateV2toV3 = MigrationStage.lightweight(
fromVersion: AppSchemaV2.self,
toVersion: AppSchemaV3.self
)
static let migrateV3toV4 = MigrationStage.custom(
fromVersion: AppSchemaV3.self,
toVersion: AppSchemaV4.self,
willMigrate: nil,
didMigrate: { context in
// Fetch all Item1 instances
let item1Descriptor = FetchDescriptor<AppSchemaV3.Item1>()
let items1 = try context.fetch(item1Descriptor)
// Fetch all Item2 instances
let item2Descriptor = FetchDescriptor<AppSchemaV3.Item2>()
let items2 = try context.fetch(item2Descriptor)
// Convert Item1 to Item3
for item in items1 {
let newItem = AppSchemaV4.Item3(name: item.name, text: "Migrated from Item1 on \(item.date)")
context.insert(newItem)
}
// Convert Item2 to Item3
for item in items2 {
let newItem = AppSchemaV4.Item3(name: item.name, text: "Migrated from Item2 with value \(item.value)")
context.insert(newItem)
}
try? context.save()
}
)
}
enum AppSchemaV1: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self]
}
@Model class Item1 {
var name: String = ""
init(name: String) {
self.name = name
}
}
}
enum AppSchemaV2: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self]
}
@Model class Item1 {
var name: String = ""
var date: Date = Date()
init(name: String) {
self.name = name
self.date = Date()
}
}
}
enum AppSchemaV3: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(3, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self, Item2.self]
}
@Model class Item1 {
var name: String = ""
var date: Date = Date()
init(name: String) {
self.name = name
self.date = Date()
}
}
@Model class Item2 {
var name: String = ""
var value: Int = 0
init(name: String, value: Int) {
self.name = name
self.value = value
}
}
}
enum AppSchemaV4: VersionedSchema {
static var versionIdentifier: Schema.Version = Schema.Version(4, 0, 0)
static var models: [any PersistentModel.Type] {
[Item1.self, Item2.self, Item3.self]
}
@Model class Item1 {
var name: String = ""
var date: Date = Date()
init(name: String) {
self.name = name
self.date = Date()
}
}
@Model class Item2 {
var name: String = ""
var value: Int = 0
init(name: String, value: Int) {
self.name = name
self.value = value
}
}
@Model class Item3 {
var name: String = ""
var text: String = ""
init(name: String, text: String) {
self.name = name
self.text = text
}
}
}
My experiment was:
To create Items for every version of the schema
Updating the typealias along the way to reflect the latest Item version.
Updating the Schema in my ModelContainer to reflect the latest Schema Version.
By AppSchemaV4, I have expected all my Items to be displayed/migrated to Item3, but it does not seem to be the case.
I can only see newly created Item3 records.
My question is, is there something wrong with how I'm doing the migrations? or are migrations not really working with CloudKit right now?
Hi! I use Tips with CloudKit and it works very well, however when a user want to remove their data from CloudKit, how to do that?
In CoreData with CloudKit area, NSPersistentCloudKitContainer have purgeObjectsAndRecordsInZone to delete both local managed objects and CloudKit records, however there is no information about the TipKit deletion.
Does anyone know ideas?
I am following Apple's instruction to sync SwiftData with CloudKit. While initiating the ModelContainer, right after removing the store from Core Data, the error occurs:
FAULT: NSInternalInconsistencyException: This NSPersistentStoreCoordinator has no persistent stores (unknown). It cannot perform a save operation.; (user info absent)
I've tried removing default.store and its related files/folders before creating the ModelContainer with FileManager but it does not resolve the issue. Isn't it supposed to create a new store when the ModelContainer is initialized? I don't understand why this error occurs. Error disappears when I comment out the #if DEBUG block.
Code:
import CoreData
import SwiftData
import SwiftUI
struct InitView: View {
@Binding var modelContainer: ModelContainer?
@Binding var isReady: Bool
@State private var loadingDots = ""
@State private var timer: Timer?
var body: some View {
VStack(spacing: 16) {
Text("Loading\(loadingDots)")
.font(.title2)
.foregroundColor(.gray)
}
.padding()
.onAppear {
startAnimation()
registerTransformers()
let config = ModelConfiguration()
let newContainer: ModelContainer
do {
#if DEBUG
// Use an autorelease pool to make sure Swift deallocates the persistent
// container before setting up the SwiftData stack.
try autoreleasepool {
let desc = NSPersistentStoreDescription(url: config.url)
let opts = NSPersistentCloudKitContainerOptions(containerIdentifier: "iCloud.my-container-identifier")
desc.cloudKitContainerOptions = opts
// Load the store synchronously so it completes before initializing the
// CloudKit schema.
desc.shouldAddStoreAsynchronously = false
if let mom = NSManagedObjectModel.makeManagedObjectModel(for: [Page.self]) {
let container = NSPersistentCloudKitContainer(name: "Pages", managedObjectModel: mom)
container.persistentStoreDescriptions = [desc]
container.loadPersistentStores { _, err in
if let err {
fatalError(err.localizedDescription)
}
}
// Initialize the CloudKit schema after the store finishes loading.
try container.initializeCloudKitSchema()
// Remove and unload the store from the persistent container.
if let store = container.persistentStoreCoordinator.persistentStores.first {
try container.persistentStoreCoordinator.remove(store)
}
}
// let fileManager = FileManager.default
// let sqliteURL = config.url
// let urls: [URL] = [
// sqliteURL,
// sqliteURL.deletingLastPathComponent().appendingPathComponent("default.store-shm"),
// sqliteURL.deletingLastPathComponent().appendingPathComponent("default.store-wal"),
// sqliteURL.deletingLastPathComponent().appendingPathComponent(".default_SUPPORT"),
// sqliteURL.deletingLastPathComponent().appendingPathComponent("default_ckAssets")
// ]
// for url in urls {
// try? fileManager.removeItem(at: url)
// }
}
#endif
newContainer = try ModelContainer(for: Page.self,
configurations: config) // ERROR!!!
} catch {
fatalError(error.localizedDescription)
}
modelContainer = newContainer
isReady = true
}
.onDisappear {
stopAnimation()
}
}
private func startAnimation() {
timer = Timer.scheduledTimer(
withTimeInterval: 0.5,
repeats: true
) { _ in
updateLoadingDots()
}
}
private func stopAnimation() {
timer?.invalidate()
timer = nil
}
private func updateLoadingDots() {
if loadingDots.count > 2 {
loadingDots = ""
} else {
loadingDots += "."
}
}
}
import CoreData
import SwiftData
import SwiftUI
@main
struct MyApp: App {
@State private var modelContainer: ModelContainer?
@State private var isReady: Bool = false
var body: some Scene {
WindowGroup {
if isReady, let modelContainer = modelContainer {
ContentView()
.modelContainer(modelContainer)
} else {
InitView(modelContainer: $modelContainer, isReady: $isReady)
}
}
}
}