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

How to access SwiftData from a background thread?
Consider a sample SwiftData project var body: some View { List(recipes) { recipe in NavigationLink(recipe.name, destination: RecipeView(recipe)) } } For SwiftUI views that uses SwiftData it's very straight forward. However, if I need to read/write to SwiftData while in the background (let's say after a network call), the app crashes. Imagine a simple workflow where (1) user selects some model objects (via SwiftData modelContext) (2) now I need to pass these objects to some API server (3) in a background thread somewhere I need to use the modelActor but it now cannot read/write to the original model objects without risk of crashing So instead, the only way I thought of is to create a separate modelContext for background processing. There I passed down the container from the main app, and creates a new ModelActor that takes in the container and owns a new modelContext of its own. This works, without crash. However it introduces many side effects: (1) read/write from this modelActor seems to trigger view changes for SwiftData's modelContext. But not vice versa. (2) model objects fetched from one context cannot be used in another context. (3) changes made in the actor also doesn’t not automatically sync with icloud but changes in SwiftData’s modelContext do. So I guess the bigger question here is, what is the proper usage of SwiftData in a background thread?
9
0
500
Sep ’24
SwiftData regression in iOS 18 RC, contexts don't sync
In iOS 18 RC, and the iOS 18 simulator shipped with Xcode 16.0 RC, there is a regression where ModelContexts on the same ModelContainer do not sync changes. A minimal example is below, but briefly: create an object in context1. Retrieve and update that object in context2, then save context2. The changes cannot be found in context1 in iOS 18 RC, but can in iOS 17 and earlier betas of iOS 18. I've submitted this as FB15092827 but am posting here for visibility to others. I'm going to have to scramble to see if I can mitigate this in our impacted app before iOS 18 launches. It's affecting us when doing background inserts in a ModelActor to populate our app UI, but you can see below the effects are seen even on the same thread in a very simple two-context example. @Test("updates sync between contexts") func crossContextSync() async throws { // overview: // create an employee in context 1 // update the employee in context 2 // check that the update is available in context 1 let context1 = ModelContext(demoAppContainer) let context2 = ModelContext(demoAppContainer) // create an employee in context 1 let newEmployee = Employee(salary: 0) context1.insert(newEmployee) try context1.save() #expect(newEmployee.salary == 0, "Created with salary 0") // update the employee in context 2 let employeeID = newEmployee.uuid let predicate: Predicate<Employee> = #Predicate<Employee> { employee in employee.uuid == employeeID } let fetchedEmployee = try #require(try? context2.fetch(FetchDescriptor<Employee>(predicate: predicate)).first) #expect(fetchedEmployee.uuid == newEmployee.uuid, "We got the correct employee in the new context") let updatedSalary = 1 fetchedEmployee.salary = updatedSalary try context2.save() // FAILURE IS HERE. This passes in earlier iOS betas and in iOS 17.X #expect(newEmployee.salary == updatedSalary, "Salary was update in context 1") // Create a new modelContext on the same container, since the container does have the changes in it. // By creating this new context we can get updated data and the test below passes in all iOS versions tested. This may be a mitigation path but creating new contexts any time you need to access data is painful. let context3 = ModelContext(demoAppContainer) let fetchedEmployeeIn3 = try #require(try? context3.fetch(FetchDescriptor<Employee>(predicate: predicate)).first) #expect(fetchedEmployeeIn3.uuid == newEmployee.uuid, "We got the correct employee in the new context3") #expect(fetchedEmployeeIn3.salary == updatedSalary, "Salary was update in context 1") } Code below if you want to build a working example, but the test above is very simple let demoAppContainer = try! ModelContainer(for: Employee.self) @main struct ModelContextsNotSyncedToContainerApp: App { init() { } var body: some Scene { WindowGroup { ContentView() .modelContainer(demoAppContainer) } } } @Model final class Employee { var uuid: UUID = UUID() var salary: Int init(salary: Int = 0) { self.salary = salary } }
4
0
457
Sep ’24
SwiftData/ModelContext.swift:3253: Fatal error: Failed to identify a store that can hold instances of SwiftData._KKMDBackingData<Presents_2024.Item> from [:]
I'm still getting this error (SwiftData/ModelContext.swift:3253: Fatal error: Failed to identify a store that can hold instances of SwiftData._KKMDBackingData<Presents_2024.Item> from [:]) in Xcode 16.1 Beta (16B5001e). The app works for a limited amount of time and then crashes with this error. It looks like the SwiftData model isn't being created properly and when a context is saved it crashes. Can you tell me if this error will be fixed in the next beta?
8
4
653
Sep ’24
SwiftData Relationship Persistence
I'm still getting started with SwiftData and having trouble understanding how to persist a model with a relationship in a container. I've tried to distill the example below to be pretty simple. Here are some example model definitions. I would like to be able to define a n Item. That Item can have SubItems related to it in a one-to-many fashion. Each SubItem is required to be attached to a parent Item. final class Item { var name: String var subitems: [Item]? init( name: String, subitems: [SubItem]? = nil ) { self.name = name } } @Model final class SubItem { var name: String init(name: String) { self.name = name } } In my app I am then defining a preview container with some pre-saved data already. Item( name: "item1", subitems: [ SubItem(name: "subItemA"), SubItem(name: "subItemB") ] ), Item( name: "item2", subitems: [ SubItem(name: "subItemC"), SubItem(name: "subItemD") ] ) ] @MainActor let PreviewContainer: ModelContainer = { do { let schema = Schema([ Item.self, SubItem.self, ]) let container = try ModelContainer( for: schema, configurations: ModelConfiguration(isStoredInMemoryOnly: true) ) for item in PreviewItems { container.mainContext.insert(item) for subItem in item.subitems! { container.mainContext.insert(subItem) } } return container } catch { fatalError("Failed to create container") } }() My app is then defined as follows... struct SwiftDataTestApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(PreviewContainer) } } struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] var body: some View { HStack { VStack { Text(items[0].name) Text(String(items[0].subitems!.count)) // Text(items[0].subitems![0].name) // Text(items[0].subitems![1].name) } Spacer() VStack { Text(items[1].name) Text(String(items[1].subitems!.count)) // Text(items[0].subitems![0].name) // Text(items[0].subitems![1].name) } } .padding(100) } } #Preview { ContentView() .modelContainer(PreviewContainer) } The preview loads without an issue, but if I uncomment the lines that access the SubItems it crashes. In the preview I can also see that each Item has 0 SubItems related to it. For some reason, the model container is not actually storing the `SubItem even though they are defined in PreviewItems. Some things I've tried Explicitly adding the relationship Adding a Item property in SubItem to link back to it's parent In the PreviewContainer definition, manually insert the SubItems as well as the parent Items Any help is appreciated, thanks
1
0
230
Sep ’24
One to Many Relationship in SwiftData
Hi, I understand how to make one to many relationship in SwiftData and how to show the child records, like all cities of a country. But how to navigate and show the parent record from a child record, Like I want to show a country of a city ? like country.cities show all cities of a country, will cities.country work to show the country ? like cities.country.name ? Kind Regards
3
0
213
Sep ’24
Swiftdata Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1cc1698ec)
Hello, i have a route with many points for routes: @Model public class Route(){ public id: UUID = UUID() var name: String var desc: String var points: [Point] = [] } @Model public class Point(){ public id: UUID = UUID() var speed: double var route : Route } when I like to add point. route.point.append(point) I get all ways this error: Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1cc1698ec) my Xcode version 15.3
0
0
283
Sep ’24
Getting a strange SwiftData error when generating samples using AVFoundation
Hi all, I'm getting a strange SwiftData error at runtime in my voice recorder app. Whenever I attempt to generate and cache a samples array so that my app can visualize the waveform of the audio, the app crashes and the following pops up in Xcode: { @storageRestrictions(accesses: _$backingData, initializes: _samples) init(initialValue) { _$backingData.setValue(forKey: \.samples, to: initialValue) _samples = _SwiftDataNoType() } get { _$observationRegistrar.access(self, keyPath: \.samples) return self.getValue(forKey: \.samples) } set { _$observationRegistrar.withMutation(of: self, keyPath: \.samples) { self.setValue(forKey: \.samples, to: newValue) } } } With an execution breakpoint on the line _$observationRegistrar.withMutation(of: self, keyPath: \.samples). Here is my model class: import Foundation import SwiftData @Model final class Recording { var id : UUID? var name : String? var date : Date? var samples : [Float]? = nil init(name: String) { self.id = UUID() self.name = name self.date = Date.now } } And here is where the samples are being generated (sorry for the long code): private func processSamples(from audioFile: AVAudioFile) async throws -> [Float] { let sampleCount = 128 let frameCount = Int(audioFile.length) let samplesPerSegment = frameCount / sampleCount let buffer = try createAudioBuffer(for: audioFile, frameCapacity: AVAudioFrameCount(samplesPerSegment)) let channelCount = Int(buffer.format.channelCount) let audioData = try readAudioData(from: audioFile, into: buffer, sampleCount: sampleCount, samplesPerSegment: samplesPerSegment, channelCount: channelCount) let processedResults = try await processAudioSegments(audioData: audioData, sampleCount: sampleCount, samplesPerSegment: samplesPerSegment, channelCount: channelCount) var samples = createSamplesArray(from: processedResults, sampleCount: sampleCount) samples = applyNoiseFloor(to: samples, noiseFloor: 0.01) samples = normalizeSamples(samples) return samples } private func createAudioBuffer(for audioFile: AVAudioFile, frameCapacity: AVAudioFrameCount) throws -> AVAudioPCMBuffer { guard let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: frameCapacity) else { throw Errors.AudioProcessingError } return buffer } private func readAudioData(from audioFile: AVAudioFile, into buffer: AVAudioPCMBuffer, sampleCount: Int, samplesPerSegment: Int, channelCount: Int) throws -> [[Float]] { var audioData = [[Float]](repeating: [Float](repeating: 0, count: samplesPerSegment * channelCount), count: sampleCount) for segment in 0..<sampleCount { let segmentStart = AVAudioFramePosition(segment * samplesPerSegment) audioFile.framePosition = segmentStart try audioFile.read(into: buffer) if let channelData = buffer.floatChannelData { let dataCount = samplesPerSegment * channelCount audioData[segment] = Array(UnsafeBufferPointer(start: channelData[0], count: dataCount)) } } return audioData } private func processAudioSegments(audioData: [[Float]], sampleCount: Int, samplesPerSegment: Int, channelCount: Int) async throws -> [(Int, Float)] { try await withThrowingTaskGroup(of: (Int, Float).self) { taskGroup in for segment in 0..<sampleCount { let segmentData = audioData[segment] taskGroup.addTask { var rms: Float = 0 vDSP_rmsqv(segmentData, 1, &rms, vDSP_Length(samplesPerSegment * channelCount)) return (segment, rms) } } var results = [(Int, Float)]() for try await result in taskGroup { results.append(result) } return results } } private func createSamplesArray(from processedResults: [(Int, Float)], sampleCount: Int) -> [Float] { var samples = [Float](repeating: 0, count: sampleCount) vDSP_vfill([0], &samples, 1, vDSP_Length(sampleCount)) for (segment, rms) in processedResults { samples[segment] = rms } return samples } private func applyNoiseFloor(to samples: [Float], noiseFloor: Float) -> [Float] { var result = samples let noiseFloorArray = [Float](repeating: noiseFloor, count: samples.count) vDSP_vsub(noiseFloorArray, 1, samples, 1, &result, 1, vDSP_Length(samples.count)) return result } private func normalizeSamples(_ samples: [Float]) -> [Float] { var result = samples var min: Float = 0 var max: Float = 0 vDSP_minv(samples, 1, &min, vDSP_Length(samples.count)) vDSP_maxv(samples, 1, &max, vDSP_Length(samples.count)) if max > min { var a: Float = 1.0 / (max - min) var b: Float = -min / (max - min) vDSP_vsmsa(samples, 1, &a, &b, &result, 1, vDSP_Length(samples.count)) } else { vDSP_vfill([0.5], &result, 1, vDSP_Length(samples.count)) } return result } And this is how the processSamples function is used: private func loadAudioSamples() async { let url = recording.fileURL if let audioFile = loadAudioFile(url: url) { if recording.samples == nil { recording.samples = try? await processSamples(from: audioFile) } } } private func loadAudioFile(url: URL) -> AVAudioFile? { do { let audioFile = try AVAudioFile(forReading: url) return audioFile } catch { return nil } } Any help or leads would be greatly appreciated! Thanks!
1
0
287
Sep ’24
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
166
Sep ’24
CloudKit container data disappeared.
Most of my CloudKit data has been missing from my container for over a week. No response from Apple support. I am very worried it may be unrecoverable.my apps can not access the data and the dashboard shows a huge amount of files and data in other files missing. These are needed to operate our chiropractic clinic and contain patient and transaction history. This might be a disaster for us.
3
0
264
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
139
Sep ’24
How can you make a TextField update text with SwiftData / CloudKit?
Hi all, I have a SwiftUI-SwiftData-CloudKit app that has a TextField. I would like to make it so that after a user types in text, it will update the TextField on another device via CloudKit. I have the following code: { private let comment:Comment @Query private var comments: [Comment] @State private var text: String = "" @State private var userInput = "" private var filteredComment: Comment? { if let comment = self.comments.first(where: { $0 == self.comment }) { return comment } else { return nil } } init(comment: Comment) { self.comment = comment } var body: some View { VStack { if let filteredComment = self.filteredComment { TextField("", text: self.$text) .onAppear() { self.text = filteredComment.text } .onChange(of: self.text) { filteredComment.text = text } } } } } This code actually works fine and will update to another device via CloudKit when a user exits out of the view and then reloads it. But I would like for it to update to the TextField after the user finishes typing or while they're typing. Weirdly, it will update on the fly in another screen I have in the app that displays the same text in a list. I've tried saving the modelContext after loading self.filteredComment and it makes no difference. Anyone know what's going on here? Thanks.
2
0
156
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
333
Sep ’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
308
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
262
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
238
Sep ’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
274
Aug ’24