iCloud & Data

RSS for tag

Learn how to integrate your app with iCloud and data frameworks for effective data storage

CloudKit Documentation

Post

Replies

Boosts

Views

Activity

Modifying SwiftData model causes deadlock
I've had a persistent but hard to reliably reproduce issue when using SwiftData with SwiftUI. Sometimes, when some properties of a SwiftData model object are used in the UI (like the title of a note in a list of notes) and something in the model is modified, the UI stops responding. The modification in the model could be a completely different object and of a different type and could be as simple as toggling a Bool, it could be performed on the main thread from inside a Button's action using the ModelContext from the environment or on a separate thread using a ModelActor. The issue even appears and disappears between builds of the same code. As an example, this is the kind of code that leads to this issue, though I haven't been able to make a minimal reproduction because of how inconsistently it appears: @ModelActor struct NoteModifier { func append() { guard let notes = try? modelContext.fetch(FetchDescriptor<Note>()) else { return } notes.randomElement()!.title.append("ABC") try? modelContext.save() } } struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var notes: [Note] var body: some View { VStack { List { ForEach(notes) { note in VStack { Text(note.title) Text(note.contents) } } } Button { Task { await NoteModifier(modelContainer: modelContext.container).append() } } label: { Text("Bug") } } } } When it happens and I try pausing execution, it's locked inside kevent_id in the getter for one of the properties of my model object like this: In Instruments:
0
0
168
Sep ’24
Best practice / pattern for displaying lists of data in CloudKit that need to update in real time?
Hi all, I have a book club app I'm developing that utilizes SwiftData and Cloudkit. From what I've learned, the way to display lists that can update in real time across multiple devices is to retrieve an array of the model objects via the Query keyword then use a computed property to sort and filter the array to your liking, and reference that computed property when you need to display the list. This works, but it seems inefficient to grab every single object of a given model and sort / filter it each time. The other method I've been trying was, for example, after the user selects a Book object that comes from a queried list populated via the method above, to pass that Book object through the program so I can access its properties to display their lists (and it should work right because that initial book property came directly from a queried array of Books?). For instance, a Book object has an array of Readers. After a user selects a Book, I can pass that Book object to the ReaderView class and display itsreaders array, and it should update in real time on CloudKit since that Book object came from a queried Books array. The benefit of this is you don't have to do as much sorting and filtering. But unfortunately, I've had mixxed results doing it this way, and it seems that the further you get away from the original object, the less success I've had getting it to update in real time. For instance, if I try to access a Reader objects Comments array property, it might not work. Anyways, I'm wondering if I should generally just keep it simple and stick to the first method I mentioned, or if there is a pattern that people generally follow?
0
0
141
Sep ’24
Deleted iCloud Records
I was on vacation last week and when I returned, I discovered that a sizable number of records in my iCloud database had been deleted! I am at a complete loss on this could have occurred. I have several questions: Is there anyway to contact Apple iCloud support teams? Does iCould have backups of data?
6
0
336
Sep ’24
SwiftData Bug with .modelContext in iOS 18
I'm using SwiftData to persist my items in storage. I used .modelContext to pass in my shared context, and on iOS 18 (both on a physical device and a simulator), I discovered a bug where SwiftData doesn't automatically save my data. For example, I could add a new item, go to the next screen, change something that reloads a previous screen, and SwiftData just forgets the item that I added. Please find the fully working code attached. While writing this post, I realized that if I use .modelContainer instead of .modelContext, the issue is solved. So I have two questions: It seems like .modelContainer is the go-to option when working with SwiftData, but why did an issue occur when I used .modelContext and passed in a shared container? When should we use .modelContext over .modelContainer? What was the bug? It's working fine in iOS 17, but not in iOS 18. Or is this expected? Here's the fully working code so you can copy and paste: import SwiftUI import SwiftData typealias NamedColor = (color: Color, name: String) extension Color { init(r: Double, g: Double, b: Double) { self.init(red: r/255, green: g/255, blue: b/255) } static let namedColors: [NamedColor] = [ (.blue, "Blue"), (.red, "Red"), (.green, "Green"), (.orange, "Orange"), (.yellow, "Yellow"), (.pink, "Pink"), (.purple, "Purple"), (.teal, "Teal"), (.indigo, "Indigo"), (.brown, "Brown"), (.cyan, "Cyan"), (.gray, "Gray") ] static func name(for color: Color) -> String { return namedColors.first(where: { $0.color == color })?.name ?? "Blue" } static func color(for name: String) -> Color { return namedColors.first(where: { $0.name == name })?.color ?? .blue } } @main struct SwiftDataTestApp: App { var sharedModelContainer: ModelContainer = { let schema = Schema([ Item.self, ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() @AppStorage("accentColor") private var accentColorName: String = "Blue" var body: some Scene { WindowGroup { NavigationStack { HomeView() } .tint(Color.color(for: accentColorName)) } .modelContainer(sharedModelContainer) // This works // .modelContext(ModelContext(sharedModelContainer)) // This doesn't work } } @Model final class Item { var timestamp: Date init(timestamp: Date) { self.timestamp = timestamp } } struct HomeView: View { @State private var showSettings = false @Environment(\.modelContext) var modelContext @AppStorage("accentColor") private var accentColorName: String = "Blue" @Query private var items: [Item] var body: some View { List { ForEach(items) { item in NavigationLink { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } Button { withAnimation { let newItem = Item(timestamp: Date()) modelContext.insert(newItem) } } label: { Image(systemName: "plus") .frame(maxWidth: .infinity) .frame(maxHeight: .infinity) } } .navigationTitle("Habits") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showSettings = true }) { Label("", systemImage: "gearshape.fill") } } } .navigationDestination(isPresented: $showSettings) { colorPickerView } } private var colorPickerView: some View { Form { Section(header: Text("Accent Color")) { Picker("Accent Color", selection: $accentColorName) { ForEach(Color.namedColors, id: \.name) { namedColor in Text(namedColor.name) .tag(namedColor.name) .foregroundColor(namedColor.color) } } .pickerStyle(.wheel) } } .navigationTitle("Settings") } }
0
3
617
Aug ’24
SwiftData unversioned migration
Hi, I'm struggling with SwiftData and the components for migration and could really use some guidance. My specific questions are Is it possible to go from an unversioned schema to a versioned schema? Do all @Model classes need to be converted? Is there one VersionedSchema for the entire app that handles all models or one VersionedSchema per model? What is the relationship, if any, between the models given to ModelContainer in a [Schema] and the models in the VersionedSchema in a [any PersistentModel.Type] I have an app in the AppStore. I use SwiftData and have four @Models defined. I was not aware of VersionedSchema when I started, so they are unversioned. I want to update the model and am trying to convert to a VersionedSchema. I've tried various things and can't even get into the migration plan yet. All posts and tutorials that I've come across only deal with one Model, and create a VersionedSchema for that model. I've tried to switch the one Model I want to update, as well as switching them all. Of course I get different errors depending on what configuration I try. It seems like I should have one VersionedSchema for the app since there is the static var models: [any PersistentModel.Type] property. Yet the tutorials I've seen create a TypeNameSchemaV1 to go with the @Model TypeName. Which is correct? An AppNameSchemaV1 which defines four models, or four TypeNameSchemaV1? Any help will be much appreciated
12
1
1.1k
Aug ’24
CloudKit SwiftData TestFlight installs vs Xcode Builds
We're using Swift Data and syncing it to iCloud. For the most part this is working. Our problem is when we delete the App, and install the latest version from TestFlight all our Swift Data Meeting tables are gone. If we create a Meeting on this instance it works, and we can display multiple meetings, but all our prior meetings are gone. Now if we just build the App in Xcode and overwrite the install, all the prior Swift Data meetings show up, in addition the above created meetings also show up. If we don't delete the App, and just install the TestFlight build over the Xcode build it also works, all the meetings show up. So it's as if there are 2 Containers, one for Development and one for Production (the TestFlight build), and they are not sync'd with each other.
2
1
276
Aug ’24
Strange behavior with 100k+ records in NSPersistentCloudKitContainer
I have been using the basic NSPersistentContainer with 100k+ records for a while now with no issues. The database size can fluctuate a bit but on average it takes up about 22mb on device. When I switch the container to NSPersistentCloudKitContainer, I see a massive increase in size to ~150mb initially. As the sync engine uploads records to iCloud it has ballooned to over 600mb on device. On top of that, the user's iCloud usage in settings reports that it takes up 1.7gb in the cloud. I understand new tables are added and history tracking is enabled but the size increase seems a bit drastic. I'm not sure how we got from 22mb to 1.7gb with the exact same data. A few other things that are important to note: I import all the 100k+ records at once when testing the different containers. At the time of the initial import there is only 1 relation (an import group record) that all the records are attached to. I save the background context only once after all the records and the import group have been made and added to the context. After the initial import, some of these records may have a few new relations added to them over time. I suppose this could be causing some of the size increase, but its only about 20,000 records that are updated. None of the records include files/ large binary data. Most of the attributes are encrypted. I'm syncing to the dev iCloud environment. When I do make a change to a single attribute in a record, CloudKit reports that every attribute has been modified (not sure if this is normal or not ) Also, When syncing to a new device, the sync can take hours - days. I'm guessing it's having to sync both the new records and the changes, but it exponentially gets slower as more records are downloaded. The console will show syncing activity, but new records are being added at a slower rate as more records are added. After about 50k records, it grinds to a halt and while the console still shows sync activity, only about 100 records are added every hour. All this to say i'm very confused where these issues are coming from. I'm sure its a combination of how i've setup my code and the vast record count, record history, etc. If anyone has any ideas it would be much appreciated.
1
0
309
Sep ’24
There is some sort of bug with iCloud container. What should I do?
I am getting the following error: Failed to save diary entry to CloudKit: <CKError 0x6000035adec0: "Server Rejected Request" (15/2000); op = 10F3CACEA9EC09B6; uuid = 22DDB0B8-9F1B-4BE6-A51B-2ADD08B469B1; container ID = "iCloud.com.domainname.productname"> What should I do to prevent this from happening? I put the right container name and still this happens. I was expecting the data model object to save in the iCloud.
0
0
264
Sep ’24
Swift Data Predicate failing for String Array
In Swift Data I have a basic model @Model class MovieSD { @Attribute(.unique) var title: String var genre: [String] = [String]() init(title: String) { self.title = title } } I am trying to create predicates when fetching the data. Creating a predicate to search by title works as expected let movieTitle = #Predicate<MovieSD> { movie in movie.title.contains("filterstring") } But when attempting to do the same for the String array let movieGenre = #Predicate<MovieSD> { movie in movie.genre.contains("filterstring") } Results in a crash with EXC_BAD_ACCESS Similar approaches produce a different error and point to the likely issue. let movieGenre2 = #Predicate<MovieSD> { movie in if movie.genre.contains(where: { $0 == "filterstring" }) { return true } else { return false } } Results in a crash with the error: error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x281a96840> , Can't have a non-relationship collection element in a subquerySUBQUERY(genre, $$_local_1, $$_local_1 == "SciFi") with userInfo of (null) or alternatively largely the same error for: let movieGenre3 = #Predicate<MovieSD> { movie in movie.genre.filter { genre in return genre == "filterstring" }.count > 0 } But I couldn't seem to find an approach to create a SUBQUERY with #Predicate Naturally, I can use similar to the above to filter the array that is returned. But this seems inefficient. And it seems it should be possible to filter Any help showing how to filter a String array with a predicate with Swift Data would be appreciated
0
0
239
Sep ’24
SwiftData does not persist change on relationship on iOS 18 beta 7
I came across of something I'm struggling to comprehend. I've got an iOS app based on SwiftUI and SwiftData + CloudKit. I wrote it using Xcode 15 and the target was iOS 17. Everything works fine in this environment, but after upgrading my phone to iOS 18 beta 7 something very strange started to happen with SwiftData on a physical device and in the simulator. Every time when data is updated, to be precise - when the relationship is modified, the change is reverted after 15 seconds! I've got the following settings on and nothing can be seen it's going on there in the logs -com.apple.CoreData.Logging.stderr 1 -com.apple.CoreData.CloudKitDebug 1 -com.apple.CoreData.SQLDebug 1 -com.apple.CoreData.ConcurrencyDebug 1 Here you are some simplified code extraction: @Model final public class Note: Identifiable, Hashable { public private(set) var uuid = UUID().uuidString var notification: Notification? ... } @Model final public class Notification: Identifiable, Hashable { var dateId: String = "" @Relationship(deleteRule: .nullify, inverse: \Note.notification) var notes: [Note]? init(_ dateId: String) { self.dateId = dateId } } @ModelActor final public actor DataModelActor : DataModel { public func updateNotification(oldDate: Date, newDate: Date? = nil, persistentModelId: PersistentIdentifier) { if let note = modelContext.model(for: persistentModelId) as? Note { updateNotification(oldDate: oldDate, newDate: newDate, note: note) } try? self.modelContext.save() } private func updateNotification(oldDate: Date? = nil, newDate: Date? = nil, note: Note) { if let oldDate = oldDate { let notifications = fetchNotifications() let oldDateId = NotificationDateFactory.getId(from: oldDate) // removing the note from the collection related to oldDate if let notification = notifications.first(where: { $0.dateId == oldDateId }) { if let notificationNotes = notification.notes { if let notificationNoteIndex = notification.notes!.firstIndex(of: note) { notification.notes!.remove(at: notificationNoteIndex) } if notification.notes == nil || notification.notes!.isEmpty { self.modelContext.delete(notification) } } } } if let newDate = newDate, newDate > Calendar.current.startOfToday() { // adding to a new collection related to newDate let notifications = fetchNotifications() let newDateId = NotificationDateFactory.getId(from: newDate) if let notification = notifications.first(where: { $0.dateId == newDateId }) { note.notification = notification } else { let notification = Notification(newDateId) note.notification = notification } } } } Spreading save method here and there does not help :( I've used Core Data Lab software to look into database and I can clearly see data changes are reverted for relationship property. Example: In Notification database there is one element: 2024-08-26 (3) with 3 notes attached. I modified one note to send notification on 2024-08-27. Changes in database reflects situation correctly showing: 2014-08-26 (2) 2024-08-27 (1) BUT!!! After 15 seconds doing noting database looks like this: 2024-08-26 (3) 2024-08-27 (0) All changes were reverted and all notes are still attached to the same date as they were at the first place. Any thoughts?
3
1
683
Aug ’24
SwiftData migration with iCloud and multi-device support
I’m writing test apps using SwiftData. Running migrations locally works fine. But I couldn’t find anything on how to handle a situation, where my local app is running on e.g. ‘MigrationSchemaV2’ and an iOS app is still running on ‘MigrationSchemaV1’ hence needs to be updated before the data migration takes place. I expect the local migration to be synced to iCloud automatically therefore the iOS app would crash. I’m looking for documentation, tutorials, best practice or lessons learned in order to understand the basic implementation idea, its dependencies and implications.
2
0
253
Aug ’24
Custom AttributedString and SwiftData
The central feature of my app requires the use of AttributedStrings with multiple custom attributes to store data about various functions related to parts of them. These AttributedString and other data also need to be persisted in SwiftData. Regarding how to do this, I spoke with an Apple engineer during WWDC24, and he said this was possible with the use of a ValueTransformer. After looking into this, I decided upon this scheme (forward direction shown here): Transform the AttributedString (with its custom attributes) into JSON data and have this interpreted as NSData, which SwiftData can persist. The value transformer seems to transform the AttributedString to NSData and back without any problems. But any attempts to use this transformer with SwiftData crashes the app. Your prompt solution to this problem would be greatly appreciated.
5
1
548
Aug ’24
app name in iCloud "Manage Storage" list is wrong
I have developed the following two App: app1 and app2. Both App have the function of using iCloud. The iCloud container id of app1 and app2 is different. I use the CloudKit storage function of iCloud. In the storage management of "Settings", iCloud, the display name of app1 is app2's name, not app1's name. This causes many of our users to delete the iCloud data of the application by mistake, resulting in losses. Now my question is: What caused the name in app1's iCloud storage management list to be displayed as the name of app2? How should it be solved?
0
0
164
Aug ’24
Xcode assets missing after adding new MacBook and iCloud.
Ever since I got a new MacBook (in 2023) and synced with my other Mac and the iCloud, all of the assets from projects created on my old Mac are missing when I open in Xcode on either computer. What's worse is this has also somehow affected my GitHub repos for these projects too. The assets are missing from my repos when cloned onto new machines. It hasn't affected assets in App Store deployment but the project which holds my deployed app was missing its assets too. I am able to locate the missing assets by digging through my Time Machine backups to find what I need and moving the folders/assets into Xcode project. Is there anyway to restore my assets in a cleaner more complete way (short of full Time Machine restore)? Why does this happen? How do I avoid this in the future?
1
0
249
Aug ’24
Has anyone successfully used NSStagedMigrationManager?
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)
6
0
1.4k
Jul ’23
Able to read from CloudKit shared database, but not write.
User A shares zone with User B (influenced from https://github.com/apple/sample-cloudkit-zonesharing, but I have just one zone "Contacts" that I am sharing): private func shareConfiguration() async throws -> (CKShare, CKContainer) { let container = CKContainer(identifier: "iCloud.com.***.syncer") let database = container.privateCloudDatabase let zone = CKRecordZone(zoneName: "Contacts") let fetchedZone = try await database.recordZone(for: zone.zoneID) guard let existingShare = fetchedZone.share else { print("Does not have existing share") let share = CKShare(recordZoneID: zone.zoneID) share[CKShare.SystemFieldKey.title] = "Resources" _ = try await database.modifyRecords(saving: [share], deleting: []) return (share, container) } print("Has existing share") guard let share = try await database.record(for: existingShare.recordID) as? CKShare else { throw NSError(domain: "", code: 0, userInfo: nil) } return (share, container) } ... let (share,container) = try! await shareConfiguration() shareView = CloudSharingView(container: container, share: share) // UIViewControllerRepresentable implementation User B accepts share invitation (borrowed from https://github.com/apple/sample-cloudkit-zonesharing) class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) { guard cloudKitShareMetadata.containerIdentifier == "iCloud.com.***.syncer" else { print("Shared container identifier \(cloudKitShareMetadata.containerIdentifier) did not match known identifier.") return } // Create an operation to accept the share, running in the app's CKContainer. let container = CKContainer(identifier: "iCloud.com.***.syncer") let operation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata]) debugPrint("Accepting CloudKit Share with metadata: \(cloudKitShareMetadata)") operation.perShareResultBlock = { metadata, result in let shareRecordType = metadata.share.recordType switch result { case .failure(let error): debugPrint("Error accepting share: \(error)") case .success: debugPrint("Accepted CloudKit share with type: \(shareRecordType)") } } operation.acceptSharesResultBlock = { result in if case .failure(let error) = result { debugPrint("Error accepting CloudKit Share: \(error)") } } operation.qualityOfService = .utility container.add(operation) } } User B through CKSyncEngine is able to read all records. However, when User B tries to write to database through CKSyncEngine, User B on his device gets following error: <CKSyncEngine 0x1282a1400> error fetching changes with context <FetchChangesContext reason=scheduled options=<FetchChangesOptions scope=all group=CKSyncEngine-FetchChanges-Automatic)>>: Error Domain=CKErrorDomain Code=2 "Failed to fetch record zone changes" UserInfo={NSLocalizedDescription=Failed to fetch record zone changes, CKPartialErrors={ "<CKRecordZoneID: 0x3024872a0; zoneName=Contacts, ownerName=_18fb98f978ce4e9c207daaa142be6024>" = "<CKError 0x30249ed60: \"Zone Not Found\" (26/2036); server message = \"Zone does not exist\"; op = DC9089522F9968CE; uuid = 4B3432A4-D28C-457A-90C5-129B24D258C0; container ID = \"iCloud.com.***.syncer\">"; }} Also, in CloudKit console, if I go to Zones, I don't see any zones under Shared Database. Wasn't I supposed to see my zone here? However, I see "Contacts" zone under Private Database. If I expand Zone details I see following: Zone wide sharing is enabled. All records in this zone are being shared with the sharing participants below. And under Participants I see both User A and User B. User B is marked as: Permission READ_WRITE Type USER Acceptance INVITED What puzzles me is why READ works, but not WRITE?
1
1
305
Aug ’24
modelContext.fetchIdentifiers(descriptor) Errors when Using a SortDescriptor
modelContext.fetchIdentifiers(descriptor) errors when using a SortDescriptor to sort by a variable and returns no models. The fetch works fine without a SortDescriptor, thus FetchDescriptor<MyModel>() works fine, but FetchDescriptor<MyModel>(sortBy: [.init(\.title)]) or FetchDescriptor<MyModel>(sortBy: [SortDescriptor(\.title)]) errors with console message The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.) I am using Xcode Version 16.0 beta 6 (16A5230g).
1
0
339
Aug ’24