I'm building a SwiftUI social photo-sharing app that uses CloudKit, where user profiles (including a CKAsset for profile pictures) are displayed throughout the app. To reduce redundant fetching of profiles across multiple views, I’m trying to implement a cache for the profile CKRecord into a custom model. (Important for handling the CKAsset for a user’s profile picture, ensuring it’s moved from the CloudKit fileURL staging area)
Here's my current approach:
struct UserProfileModel: Identifiable {
let id: String
let displayUsername: String
var profilePicture: UIImage? = nil
}
class UserProfileCache: ObservableObject {
static let shared = UserProfileCache()
@Published var cache: [UserProfileModel] = []
}
Is this a solid approach for caching CKRecords, or is there a more efficient way to structure this for performance and memory management?
I'd appreciate any input or advice on improving this architecture for performance, memory management, and handling profile updates.
Thanks in advance for your help!
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 would like to create a private container and share a zone between two users with different iCloud accounts. All changes made by one would be notified with push notifications to the other user's db. Both could change the same information.
Exactly as it is done in this apple project.
https://developer.apple.com/documentation/cloudkit/shared_records/sharing_cloudkit_data_with_other_icloud_users
However, I have been reading this code for days and I am stuck on it, it is extremely complicated for my level.
I would really like to know if there is any simple project that uses the same idea to build this logic with swiftui.
Hello all!
I'm porting a ios15+ swiftui app to be compatible with Swift 6 and enabling strict concurrency checking gave me a warning that will be an error when switching to swift 6.
I'm initializing a persistence controller for my cloud kit container:
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init() {
container = NSPersistentCloudKitContainer(name: "IBreviary")
container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
The warning is on the merge policy:
Reference to var 'NSMergeByPropertyObjectTrumpMergePolicy' is not concurrency-safe because it involves shared mutable state; this is an error in the Swift 6 language mode
I have no idea how to make this concurrency safe, nor I found a documentation entry to help me with this.
Anyone have idea how to solve this?
Thanks in advance
V.
Hi,
We are currently planning an app transfer between two developer accounts.
We are concerned about files stored in the app's documents surviving the first update of the app released with the new developer account.
Since app files in documents are part of the app's container, is it safe to assume that if the user just updates the app after the transfer, the files in documents would still be there? It is important for us to confirm this before we execute our plans
Also, our app currently uses iCloud containers to save another set of files. Are these transferred with the app transfer or will the app lose access to these files? Are the files however accessible by users' by looking for them on their iCloud Drive?
Thank you!
I’m developing an app for inspections that allows users to develop their own template for inspections. Because of this, my data structure has become more than a little complex (see below).
This structure does allow a great deal in flexibility, through, as users can add groups, rows, and individual fields as needed. For example, users can add a header group, then a row to the header with fields for the Building Number, Unit Number, & Inspection Date.
However, I don’t have an efficient way to sort the inspections by the values in these fields. SwiftData sorting is keypath based, which won’t allow me to sort the inspections based on the values only in fields with specific labels.
As an alternative, I can query for fields with a specific label and do a compactMap to the inspection. But this doesn’t scale well when working with potentially hundreds or thousands of inspections as compactMap takes a lot longer then the query takes. It also doesn’t work well if I want to filter inspections or sort using values in multiple user defined fields.
Models below are greatly simplified to just get the point across. More than happy to provide some additional code if asked when I’m back at my laptop with the source code. (Typing this on my iPad at the moment)
@Model final class Inspection {
// init and other fields
var groups: [Group]?
}
@Model final class Group {
// init and other fields
@Relationship(inverse: \Inspection.groups)
var inspection: Inspection?
var rows: [Row]?
}
@Model final class Row {
// init and other fields
@Relationship(inverse: \Group.rows)
var group: Group?
var fields: [Field]?
}
@Model final class Field {
// init and other fields
var label: String?
var type: FieldType // enum, denoting what type of data this is storing
var stringValue: String?
var boolValue: Bool?
var dateValue: Date?
@Attribute(.externalStorage) var dataValue: Data?
@Relationship(inverse: \Row.fields)
var row: Row?
}
Hello everyone,
Xcode 16.0 SwiftData project. CloudKit. WidgetConfigurationIntent.
For some reason, I see a really weird behavior.
I have a shared ModelContainer and an interactive widget where I update the model data through an app intent.
This is my model -
@MainActor
class ItemsContainer {
static let shared = ItemsContainer()
var sharedModelContainer: ModelContainer!
init() {
self.sharedModelContainer = container()
}
func container() -> ModelContainer? {
if let sharedModelContainer {
return sharedModelContainer
}
let schema = Schema([
Session.self,
])
let modelConfiguration: ModelConfiguration
modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false, cloudKitDatabase: .automatic)
do {
let container = try ModelContainer(for: schema, configurations: [modelConfiguration])
self.sharedModelContainer = container
return container
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}
}
And this is how I get the model context across the app and the app intent -
let modelContext = ModelContext(ItemsContainer.shared.sharedModelContainer)
The problem is that somehow, when I update the model context in the app and then in the widget (I save the context after every change), the data is synced between the app and the widget, but then, the data is changed back to the previous state and kind of ignores the widget changes.
Didn't happen before iOS 18/Xcode 16.
Any idea?
Thanks a lot!
I have been investigating a crash where saving a context leads to a crash triggered internally by Core Data. Is there any information on what exception or fatal error it is? Or any hint of any kind which leads to NSManagedObjectContext.m:1475 (seems like this line calls abort or some sort of fatal error). Seems like this happens when the app is in background.
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x000000019266a3f8
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Terminating Process: exc handler [38173]
Triggered by Thread: 9
…
Thread 9 Crashed:
0 CoreData 0x000000019266a3f8 -[NSManagedObjectContext _thereIsNoSadnessLikeTheDeathOfOptimism] + 36 (NSManagedObjectContext.m:1475)
1 CoreData 0x00000001925c8fa0 -[NSManagedObjectContext save:] + 1844 (NSManagedObjectContext.m:1688)
2 MyApp 0x0000000102cc747c closure #1 in DatabaseContainer.write(_:completion:) (in MyApp) (DatabaseContainer.swift:233) + 7861372
3 MyApp 0x0000000102baab14 thunk for @escaping @callee_guaranteed () -> () (in MyApp) (<compiler-generated>:0) + 6695700
4 CoreData 0x000000019252dfe8 developerSubmittedBlockToNSManagedObjectContextPerform + 156 (NSManagedObjectContext.m:3985)
5 libdispatch.dylib 0x00000001922e2dd4 _dispatch_client_callout + 20 (object.m:576)
6 libdispatch.dylib 0x00000001922ea400 _dispatch_lane_serial_drain + 748 (queue.c:3900)
7 libdispatch.dylib 0x00000001922eaf30 _dispatch_lane_invoke + 380 (queue.c:3991)
8 libdispatch.dylib 0x00000001922f5cb4 _dispatch_root_queue_drain_deferred_wlh + 288 (queue.c:6998)
9 libdispatch.dylib 0x00000001922f5528 _dispatch_workloop_worker_thread + 404 (queue.c:6592)
10 libsystem_pthread.dylib 0x00000001e6e8c934 _pthread_wqthread + 288 (pthread.c:2696)
11 libsystem_pthread.dylib 0x00000001e6e890cc start_wqthread + 8 (:-1)
Consider this code
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
init() {
let schema = Schema([
...
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
sharedModelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
SettingsViewModel.shared = SettingsViewModel(modelContext: sharedModelContainer.mainContext)
}
I'm basically saving a copy of mainContext in a viewModel. And then later on uses that viewModel to operate on the models while using the mainActor.
Is this ok? That same container is also pass into the view using
.modelContainer(sharedModelContainer)
Can it be used in both ways like that?
I have a Live Activity with a button that updates a SwiftData model. This used to work in iOS 17, but not on iOS 18. The reason is that in iOS 17, when you run an AppIntent from a Live Activity, the perform() method would run in the main app's process, meaning it had access to the app's ModelContainer/ModelContext. However, in iOS 18, this is no longer the case, and the perform() method of an AppIntent now runs in the extension's process.
While I can still construct a new ModelContainer & ModelContext in the AppIntent's perform() method, the main app's container and context will not see these changes until the app is relaunched.
How can I make this work in iOS 18 now that an AppIntent executed from an extension runs in a different process from the main app?
I'm using NSPersistentCloudKitContainer and in the CloudKit dashboards I have added indexes for all my records modifiedTimestamp queryable, modifiedTimestamp sortable and recordName queryable.
But I'm still getting this warning message in the console.
<CKError 0x302acf0c0: "Invalid Arguments" (12/2015); server message = "Field 'recordName' is not marked queryable"; op = FF68EFF8D501AED8; uuid = 12C5C84B-EA9B-41A6-AD85-34023827E6FA; container ID = "z.y.x">
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _importFinishedWithResult:importer:](1400): <PFCloudKitImporter: 0x30316c1c0>: Import failed with error:
<CKError 0x302acf0c0: "Invalid Arguments" (12/2015); server message = "Field 'recordName' is not marked queryable"; op = FF68EFF8D501AED8; uuid = 12C5C84B-EA9B-41A6-AD85-34023827E6FA; container ID = "z.y.x">
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate recoverFromError:](2312): <NSCloudKitMirroringDelegate: 0x301b1cd20> - Attempting recovery from error: <CKError 0x302acf0c0: "Invalid Arguments" (12/2015); server message = "Field 'recordName' is not marked queryable"; op = FF68EFF8D501AED8; uuid = 12C5C84B-EA9B-41A6-AD85-34023827E6FA; container ID = "z.y.x">
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _recoverFromError:withZoneIDs:forStore:inMonitor:](2622): <NSCloudKitMirroringDelegate: 0x301b1cd20> - Failed to recover from error: CKErrorDomain:12
Recovery encountered the following error: (null):0
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](612): <NSCloudKitMirroringDelegate: 0x301b1cd20> - resetting internal state after error: <CKError 0x302acf0c0: "Invalid Arguments" (12/2015); server message = "Field 'recordName' is not marked queryable"; op = FF68EFF8D501AED8; uuid = 12C5C84B-EA9B-41A6-AD85-34023827E6FA; container ID = "z.y.x">
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _requestAbortedNotInitialized:](2200): <NSCloudKitMirroringDelegate: 0x301b1cd20> - Never successfully initialized and cannot execute request '<NSCloudKitMirroringImportRequest: 0x300738eb0> A3F23AAC-F820-4044-B4B9-28DFAC4DE8D7' due to error: <CKError 0x302acf0c0: "Invalid Arguments" (12/2015); server message = "Field 'recordName' is not marked queryable"; op = FF68EFF8D501AED8; uuid = 12C5C84B-EA9B-41A6-AD85-34023827E6FA; container ID = "z.y.x">
I have an app that starts a Live Activity on a certain user action. This Live Activity contains a button that the user can tap, which updates a SwiftData model instance. However, when you return to the main app after tapping the button on the Live Activity, the views do not update to reflect the changes, even though the changes were written to the database.
The underlying issue here is that the ModelContainer/ModelContext used by the AppIntent (performed from the LiveActivity when the button is tapped), are different from the instances in the main app. Meaning that while the changes are written to the underlying storage, the in-memory instances of ModelContext/ModelContainer in the main app don't get the changes from the extension, so SwiftUI doesn't update either.
What is the recommended way to handle this scenario? Or is there one? :) Shared access to a SwiftData container is clearly supported through App Groups, so is there not a mechanism to ensure changes made by an extension are updated in real-time for the main app?
Otherwise, it seems I would have to go through and manually rerun queries that views depend on to make sure they are showing the most recent data. This is cumbersome and error-prone.
Perhaps I'm missing something? Any suggestions would be greatly appreciated.
I'm trying to safely perform the apparently complex task for a cloud storage API, namely "downloading files", but it seems like iCloud APIs are comically broken beyond repair:
-[NSFileCoordinator coordinateAccessWithIntents:queue:byAccessor:] calls the accessor block before all files have finished downloading.
The same API will also return success (called the block with error == nil) even if the download fails (e.g. the phone is in airplane mode). I both cases, the files requested by the intents will not exist.
-[NSFileManager startDownloadingUbiquitousItemAtURL:error:] does not have a completion block (Why?!?!)
Similarly, this API will return success even if it fails (e.g. airplane mode)
Manually checking NSURLUbiquitousItemIsDownloadingKey is broken as well, failed downloads (e.g. Airplane mode again) will retain their "Downloading" status, and NSURLUbiquitousItemDownloadingErrorKey is never updated.
How can one safely download a file from iCloud if all of the APIs are broken?
Hello everyone,
I’ve recently encountered an issue where my app is working perfectly fine, but I’m seeing an “OTHER” error in the CloudKit dashboard under errors. I’ve checked the logs and there doesn’t seem to be any obvious failure or issue affecting the app’s functionality.
The error doesn’t provide much detail, and I’m having trouble identifying the root cause since everything appears to be functioning as expected in the app. Has anyone else experienced this? Is this something that could be related to a server-side issue, or am I missing something on my end?
Any insights or advice would be greatly appreciated!
Thanks in advance!
With Core Data and SwiftUI we can use @SectionedFetchRequest. Does SwiftData support something similar to @SectionedFetchRequest?
For example, I want to create a lazy-loaded list that groups posts by their date.
@Model Post {
let title: String
let dateString: String // YYYY-MM-DD
let createdAt: Date
}
@SectionedFetchRequest(
entity: \Post.self,
sectionIdentifier: \Post.dateString,
sortDescriptors: [\Post.createdAt]
)
var postsByDate: SectionedFetchResults
ForEach(postsByDate) { section in
Section(header: Text(section.id)) {
ForEach(section) { post in
PostView(post)
}
}
}
Hey developers! I updated to Xcode 16 and iOS 18. I wanted to publish my first iOS 18 update but I keep getting a very strange error after building and launching the app: "Fatal error: This model instance was destroyed by calling ModelContext.reset and is no longer usable." (I haven't changed anything regarding swift data and I never call ModelContext.reset)
This error happens only after building. When I close the app and open it again (without running it through Xcode) the app never crashes and all the data is still there. I couldn't find much bout this error online. Is anyone experiencing the same?
I wonder if this is a bug in Xcode 16 or there is something wrong with my code. I also wonder if I can safely publish my update to App Store, since the error only happens after building. Thank you!
Is it possible to track history using the new HistoryDescriptor feature in SwiftData? Or can I only get the current most recent data? Or is it possible to output the changed data itself, along with timestamps?
I am hoping that it is possible to track by a standard feature like NSPersistentHistoryTransaction in CoreData.
Do we still have to use a method in SwiftData that creates more tracking data itself?
I'm seeing a lot of these in my logs:
PersistentIdentifier PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-swiftdata://Course/BC9CF99A-DE6A-46F1-A18D-8034255A56D8), implementation: SwiftData.PersistentIdentifierImplementation) was remapped to a temporary identifier during save: PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-coredata:///Course/t58C849CD-D895-4773-BF53-3F63CF48935B210), implementation: SwiftData.PersistentIdentifierImplementation). This is a fatal logic error in DefaultStore
... though everything seems to work.
Does anyone know what this means in this context? Anything I can do to not have this appear?
Here we have yet another bug, I suppose, in SwiftData that happens on iOS18 but it is not an issue on iOS17.
There are 2 models defined as follows
@Model
final public class Note: Identifiable, Codable, Hashable
{
public private(set) var uuid = UUID().uuidString
var heading: String = ""
var tags: [Tag]?
init(heading: String = "") {
self.heading = heading
}
required public init(from decoder: Decoder) throws {
...
}
public func encode(to encoder: Encoder) throws {
...
}
}
@Model
final public class Tag: Identifiable, Codable
{
var name: String = ""
@Relationship(deleteRule: .nullify, inverse: \Note.tags) var notes: [Note]?
init(_ name: String) {
self.name = name
}
required public init(from decoder: Decoder) throws {
…
}
public func encode(to encoder: Encoder) throws {
...
}
}
and a function o add new tags as follows
private func addTags(note: Note, tagNames: [String]) {
if note.tags == nil {
note.tags = []
}
for tagName in tagNames {
if let tag = fetchTag(tagName) {
if !note.tags!.contains(where: {$0.name == tagName}) {
note.tags!.append(tag)
}
} else {
// The following line throws the exception on iOS18 when Tag conforms to Codable:
// Illegal attempt to map a relationship containing temporary objects to its identifiers.
note.tags!.append(Tag(tagName))
}
}
}
This code works perfectly well on iOS17 but on iOS18 I get the exception “Illegal attempt to map a relationship containing temporary objects to its identifiers.”
What I noticed that this happens only when Tag model conforms to Codable protocol. Is it a bug? It looks like, otherwise we've got some undocumented changes have been made.
In my previous post I mentioned about the other issue about ModelContext that is broken too on iOS18 - I mean it works perfectly well on iOS17.
Demo app with an example how to workaround this problem is available here on GitHub.
Repro steps:
Add a note with some tags (separated by space)
Edit this note and add a new tag (tag that does not exists in database) and tap Save.
You should noticed that the tag hasn't been added. It works occasionally but hardly to be seen.
Hello,
I’m struggling to go from unversioned data model in SwiftData, to starting to version it.
Some FYI:
I’m using CloudKit
I’m using a widget, where I also pass in my data model and setup my container, this is shared over a group container/app group.
My migration is very simple, I’m adding a property which is not optional ( has default value set, and a default value in initialiser ).
Model:
@Model
class NicotineModel {
var nicotineType: NicotineType = NicotineType.snus
var startDate: Date = Date() + 30
var spendingAmount: Int = 0
var nicotinePerDay: Int = 0
var quittingMethod: QuittingMethod = QuittingMethod.coldTurkey // this is the change in the model, V1 doesn't have the quittingMethod property
var setupComplete: Bool = false
I’ve tried with:
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self
)
But also
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: nil,
didMigrate: {
context in
let nicotineModels2 = try context.fetch(FetchDescriptor<SchemaV2.NicotineModel>())
let nicotineModels = try context.fetch(FetchDescriptor<SchemaV1.NicotineModel>())
for model in nicotineModels {
let newModel = SchemaV2.NicotineModel(
nicotineType: model.nicotineType,
startDate: model.startDate,
spendingAmount: model.spendingAmount,
nicotinePerDay: model.nicotinePerDay,
setupComplete: model.setupComplete,
quittingMethod: .coldTurkey
)
context.insert(newModel)
context.delete(model)
}
try context.save()
}
)
and simply
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: nil,
didMigrate: { context in
let nicotineModels = try context.fetch(FetchDescriptor<SchemaV2.NicotineModel>())
for model in nicotineModels {
model.quittingMethod = .coldTurkey
}
try context.save()
}
)
This gives me the error on startup
SwiftData/ModelCoders.swift:1762: Fatal error: Passed nil for a non-optional keypath \NicotineModel.quittingMethod
On https://icloud.developer.apple.com I can see that the record doesn't include my quittingMethod.
I'm loosing my mind, what am I doing wrong?
Hi Folks,
starting with iOS18 and using Xcode16, accessing fetchedProperties results in an error. I identified the issue to occur as soon as the initialization of a fetched property with external binary data storage starts.
Console output during debugging:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This expression has evaluation disabled'
*** First throw call stack:
[...]
libc++abi: terminating due to uncaught exception of type NSException
Console output when trying to "print" the item via the contact menu of the debugger:
Printing description of variable:
error: error: Execution was interrupted, reason: internal ObjC exception breakpoint(-6)..
The process has been returned to the state before expression evaluation.
Message from debugger: killed
The identical code works with iOS before iOS 18 (same for iPadOS 18).
Does anyone observed a similar issue and figured out a solution already?
Cheers,
folox