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

SwiftData - Problems with Predicates and Relations
Hello, I have a problem with SwiftData and Predicates that check for the persistentModelID of the relations. My data model looks simplified like this: Day -> TimeEntry[] -> Hashtag[] What I want to achieve is to query the days and associated time entries via assigned tags. This is my predicate: let identifier = filterHashtags.map(\.persistentModelID) ... #Predicate<TimeEntry> { timeEntry in identifiers.count == timeEntry.tags.filter { tag in identifiers.contains(tag.persistentModelID) }.count } It does not return any data when I check for the persistentModelID. However, if I use another property of the tags, e.g. the name or a generated UUID for the check, the predicate works. Is this a general problem with PersistentIdentifier in Predicates or am I missing something? Thanks in advance
3
0
213
Oct ’24
SwiftData thread-safety: passing models between threads
Hello, I'm trying to understand how dangerous it is to read and/or update model properties from a thread different than the one that instantiated the model. I know this is wrong when using Core Data and we should always use perform/performAndWait before manipulating an object but I haven't found any information about that for SwiftData. Question: is it safe to pass an object from one thread (like MainActor) to another thread (in a detached Task for example) and manipulate it, or should we re fetch the object using its persistentModelID as soon as we cross threads? When running the example app below with the -com.apple.CoreData.ConcurrencyDebug 1 argument passed at launch enabled, I don't get any Console warning when I tap on the "Update directly" button. I'm sure it would trigger a warning if I were using Core Data. Thanks in advance for explaining. Axel -- @main struct SwiftDataPlaygroundApp: App { var body: some Scene { WindowGroup { ContentView() .modelContainer(for: Item.self) } } } struct ContentView: View { @Environment(\.modelContext) private var context @Query private var items: [Item] var body: some View { VStack { Button("Add") { context.insert(Item(timestamp: Date.now)) } if let firstItem = items.first { Button("Update directly") { Task.detached { // Not the main thread, but firstItem is from the main thread // No warning in Xcode firstItem.timestamp = Date.now } } Button("Update using persistentModelID") { let container: ModelContainer = context.container let itemIdentifier: Item.ID = firstItem.persistentModelID Task.detached { let backgroundContext: ModelContext = ModelContext(container) guard let itemInBackgroundThread: Item = backgroundContext.model(for: itemIdentifier) as? Item else { return } // Item on a background thread itemInBackgroundThread.timestamp = Date.now try? backgroundContext.save() } } } } } } @Model final class Item: Identifiable { var timestamp: Date init(timestamp: Date) { self.timestamp = timestamp } }
1
1
360
Oct ’24
UICloudSharingContainer equivalent on macOS (i.e. SwiftUI)?
I'm trying to write a SwiftUI iOS/macOS app that allows users to collaborate, but I keep running into limitations. The latest is that I can't figure out what the UICloudSharingContainer equivalent is on macOS. It doesn't seem like there’s a SwiftUI version of this, so I have to write a lot of platform-specific code to handle it, but it's not clear what the AppKit equivalent is.
4
0
224
Oct ’24
ValueTransformer currently crashes XCode SwiftUI preview
I have a working ValueTransformer that runs fine in simulator/device, but crashes in SwiftUI Preview. Even though they are the same code. Here is my code import Foundation final class StringBoolDictTransformer: ValueTransformer { override func transformedValue(_ value: Any?) -> Any? { guard let stringBoolDict = value as? [String: Bool] else { return nil } let nsDict = NSMutableDictionary() for (key, bool) in stringBoolDict { nsDict[key] = NSNumber(value: bool) } do { let data = try NSKeyedArchiver.archivedData(withRootObject: nsDict, requiringSecureCoding: true) return data } catch { debugPrint("Unable to convert [Date: Bool] to a persistable form: \(error.localizedDescription)") return nil } } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? Data else { return nil } do { guard let nsDict = try NSKeyedUnarchiver.unarchivedDictionary(ofKeyClass: NSString.self, objectClass: NSNumber.self, from: data) else { return nil } var result = [String: Bool]() for (key, value) in nsDict { result[key as String] = value.boolValue } return result } catch { debugPrint("Unable to convert persisted Data to [Date: Bool]: \(error.localizedDescription)") return nil } } override class func allowsReverseTransformation() -> Bool { true } override class func transformedValueClass() -> AnyClass { NSDictionary.self } } and here is the container public struct SwiftDataManager { public static let shared = SwiftDataManager() public var sharedModelContainer: ModelContainer init() { ValueTransformer.setValueTransformer( StringBoolDictTransformer(), forName: NSValueTransformerName("StringBoolDictTransformer") ) let schema = Schema([, Plan.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { sharedModelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } } } and the model @Model final class Plan { @Attribute(.transformable(by: StringBoolDictTransformer.self)) var dict: [String: Bool] = [:] } I would get that container and pass it in appdelegate and it works fine. I would get that container and pass it inside a #Preview and it would crash with the following: Runtime: iOS 17.5 (21F79) - DeviceType: iPhone 15 Pro CoreFoundation: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "dict"; desired type = NSDictionary; given type = _NSInlineData; value = {length = 2, bytes = 0x7b7d}.' libsystem_c.dylib: abort() called Version 16.0 (16A242d)
2
0
268
Oct ’24
Swiftdata cloudkit synchronization issues
Hi, I did cloudkit synchronization using swiftdata. However, synchronization does not occur automatically, and synchronization occurs intermittently only when the device is closed and opened. For confirmation, after changing the data in Device 1 (saving), when the data is fetched from Device 2, there is no change. I've heard that there's still an issue with swiftdata sync and Apple is currently troubleshooting it, is the phenomenon I'm experiencing in the current version normal?
1
0
181
Oct ’24
SwiftData: var dates: [Date]? or var dates: [Date] = []
I am trying to get my head around SwiftData, and specifically some more "advanced" ideas that I have not seen covered in the various tutorials. Specifically, I have a class that includes a collection that may or may not contain elements. For now I am experimenting with a simple array of Date, and I don't know if I should make it an optional, or an empty array. Without SwiftData in the mix it seems like it's probably programmers choice, but I wonder if SwiftData handles those two scenarios differently, that would suggest one over the other.
2
0
250
Oct ’24
SwiftData and CloudKit
Recently I've been working on a demo project called iLibrary. The main goal was to learn more about CloudKit and SwiftData. After a while I noticed that there were some hangs/freezes when running the app in debug mode. I first tried this with Xcode 15.4 and iOS 17.5. Here the hang only appears at the beginning, but only for a few seconds. But when I exit debug mode, there are no more hangs. With Xcode 16 beta 4 and iOS 18 it looks completely different. In this case, the hangs and freezes are always present, whether in debug mode or not. And it's not just at the beginning, it's throughout the app. I'm aware that this is still a beta, but I still find this weird. And when I profile this I see that the main thread gets quite overloaded. Interestingly, my app doesn't have that many operations going on. So I guess something with the sync of SwiftData or my CloudKitManger where I fetch some records from the public database is not running fine. Lastly, I wanted to delete the iCloud app data. So I went to Settings and tried to delete it, but it didn't work. Is this normal? Does anyone have any idea what this could be? Or has anyone encountered this problem as well? I'd appreciate any support. My project: https://github.com/romanindermuehle/iLibrary
5
3
602
Aug ’24
Code Review: SwiftData functions in a Service, Model functions in a Manager
First off, given that I didn't find a tag for Code Review, I hope I am not out of scope for the forums here. Second, some background. I am a long time Windows Power Shell developer, moving to Swift because I don't like self loathing. :) Currently I am trying to get my head around SwiftData, and experimenting with creating a Service to handle the actual SwiftData functionality, and a Manager to handle various tasks that relate to instances of the Model. I am doing this realizing that it MAY NOT be the best approach, but it gives me reps both producing code and thinking about how to solve a problem, which I think is useful even if the actual product in throw away. That said, I am hoping someone with more experience than I can comment on this approach, especially with respect to expanding to more models, more complex models, lots of data and a desire to use ModelActor eventually. DataManagerApp.swift import SwiftData import SwiftUI @main struct DataManagerApp: App { let container: ModelContainer init() { let schema = Schema([DataModel.self]) let config = ModelConfiguration("SwiftDataStore", schema: schema) do { let modelContainer = try ModelContainer(for: schema, configurations: config) DataService.instance.assignContainer(modelContainer) container = modelContainer } catch { fatalError("Could not configure SwiftData ModelContainer.") } } var body: some Scene { WindowGroup { ContentView() .modelContainer(container) } } } DataModel.swift import Foundation import SwiftData @Model final class DataModel { var date: Date init(date: Date) { self.date = date } } final class DataService { static let instance = DataService() private var modelContainer: ModelContainer? private var modelContext: ModelContext? private init() {} func assignContainer(_ container: ModelContainer) { if modelContainer == nil { modelContainer = container modelContext = ModelContext(modelContainer!) } else { print("Attempted to assign ModelContainer more than once.") } } func addModel(_ dataModel: DataModel) { modelContext?.insert(dataModel) } func removeModel(_ dataModel: DataModel) { modelContext?.delete(dataModel) } } final class ModelManager { static let instance = ModelManager() let dataService: DataService = DataService.instance private init() {} func newModel() { let newModel = DataModel(date: Date.now) DataService.instance.addModel(newModel) } } ContentView.swift import SwiftData import SwiftUI struct ContentView: View { @Environment(\.modelContext) var modelContext @State private var sortOrder = SortDescriptor(\DataModel.date) @Query(sort: [SortDescriptor(\DataModel.date)]) var models: [DataModel] var body: some View { VStack { addButton List { ForEach(models) { model in modelRow(model) } } .listStyle(.plain) } .padding() .toolbar { ToolbarItem(placement: .topBarTrailing) { addButton } } } } private extension ContentView { var addButton: some View { Button("+ Add") { ModelManager.instance.newModel() } } func modelRow(_ model: DataModel) -> some View { HStack { Text(model.date.formatted(date: .numeric, time: .shortened)) Spacer() } } }
0
0
177
Oct ’24
CKSyncEngine with dependent CKRecords
Hi, when using CKSynEgine it is the responsibility of the app to implement CKSyncEngineDelegate. One of the methods of CKSyncEngineDelegate is nextFetchChangesOptions. The implementation of this method should return a batch of CKRecords so that CKSyncEngine can do the syncing whenever it thinks it should sync. A simple implementation might look like this: func nextRecordZoneChangeBatch( _ context: CKSyncEngine.SendChangesContext, syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch?{ await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: syncEngine.state.pendingRecordZoneChanges) { recordID in // here we should fetch to local representation of the value and map it to a CKRecord } } The problem I am having is as follows: If the CKRecords I am returning in a batch have dependencies between each other (using CKRecord.Reference or the parent property) but are not part of the same batch, the operation could fail. And as far as I understand, there is no way to prevent this situation because: A: The batch I can return is limited in size. If the number of CKRecords is too large, I have to split them into multiple batches. B: Splitting them is arbitrary, since I only have the recordID at this point, and there is no way to know about the dependencies between them just by looking at the recordID. So basically my question is: how should the implementation of nextRecordZoneChangeBatch look like to handle dependencies between CKRecords?
4
0
454
Aug ’24
ValueTransformer: Unable to determine the primitive for Attribute Error
I've been struggling to get a ValueTransformer to work while developing in Xcode 16 for iOS 18. Despite thinking I had everything set up correctly, I keep encountering the following error whenever I create a tag: let tag = Tag(name: name, color: color.toPlatformColor()) // Converts it to NSColor or UIColor modelContext.insert(tag) SwiftData/DataUtilities.swift:184: Fatal error: Unable to determine the primitive for Attribute - name: color, options: [transformable with Optional("ColorTransformer")], valueType: UIColor, defaultValue: UIExtendedSRGBColorSpace 0 0 1 1, hashModifier: nil Here’s what I’m dealing with: Tag Model: @Model public final class Tag: Identifiable { var name: String = "" @Attribute(.transformable(by: ColorTransformer.self)) var color: PlatformColor = PlatformColor.blue init(name: String, color: PlatformColor) { self.name = name self.color = color } } ColorTransformer: final class ColorTransformer: ValueTransformer { override func transformedValue(_ value: Any?) -> Any? { guard let color = value as? PlatformColor else { return nil } do { let data = try NSKeyedArchiver.archivedData( withRootObject: color, requiringSecureCoding: true) return data } catch { assertionFailure("Failed to transform `PlatformColor` to `Data`") return nil } } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? NSData else { return PlatformColor.black } do { let color = try NSKeyedUnarchiver.unarchivedObject( ofClass: PlatformColor.self, from: data as Data) return color } catch { assertionFailure("Failed to transform `Data` to `PlatformColor`") return nil } } override class func transformedValueClass() -> AnyClass { return PlatformColor.self } override class func allowsReverseTransformation() -> Bool { return true } public static func register() { ValueTransformer.setValueTransformer(ColorTransformer(), forName: .colorTransformer) } } extension NSValueTransformerName { static let colorTransformer = NSValueTransformerName(rawValue: "ColorTransformer") } Platform Alias: #if os(macOS) typealias PlatformColor = NSColor #else typealias PlatformColor = UIColor #endif The ValueTransformer is registered when the ModelContainer is created at app startup: var sharedModelContainer: ModelContainer = { ColorTransformer.register() // Other configurations... }() I've also tried not aliasing the colors to see if that changes anything, but I still encounter the same issue. Any guidance or suggestions would be greatly appreciated!
0
0
136
Oct ’24
SwiftData relationship crash on 17.x
Is this Relationship correct? Does this cause a circular reference? This runs on 18 but crashes on 17 in the swift data internals. @Model final class Item { @Attribute(.unique) var id: UUID var date: Date @Relationship(deleteRule: .nullify, inverse: \Summary.item) var summary: Summary? init(date: Date = Date.now) { self.id = UUID() self.date = Calendar.current.startOfDay(for: date) self.summary = Summary(self) } } @Model final class Summary { @Attribute(.unique) var id = UUID() @Relationship var item: Item? init(_ item: Item) { self.item = item } }
5
0
276
Oct ’24
Any way to force-refresh @Query properties in SwiftUI?
I'm wondering if there is a way to force a re-fetch of a @Query property inside of a SwiftUI view so I could offer a pull-to-refresh mechanism for users to force-refresh a view. Why would I want this? iOS 18.0 and 18.1 currently contain some regressions that prevent SwiftData from properly gathering model updates caused by ModelActor's running on background threads and the suggested workarounds (listening for .NSManagedObjectContextDidSave) don't work well in most scenarios and do not cause queries in the current view to be fully re-evaluated (see Importing Data into SwiftData in the Background Using ModelActor and @Query).
0
0
227
Oct ’24
CKSyncEngine.RecordZoneChangeBatch and the CKSyncEngineDelegate protocol
I'm having some trouble with the following function from the CKSyncEngineDelegate protocol. func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext, syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? { The sample code from the documentation is func nextRecordZoneChangeBatch( _ context: CKSyncEngine.SendChangesContext, syncEngine: CKSyncEngine ) async -> CKSyncEngine.RecordZoneChangeBatch? { // Get the pending record changes and filter by the context's scope. let pendingChanges = syncEngine.state.pendingRecordZoneChanges .filter { context.options.zoneIDs.contains($0) } // Return a change batch that contains the corresponding materialized records. return await CKSyncEngine.RecordZoneChangeBatch( pendingChanges: pendingChanges) { self.recordFor(id: $0) } } init?(pendingChanges: [CKSyncEngine.PendingRecordZoneChange], recordProvider: (CKRecord.ID) -> (CKRecord?)) works fine for the sample app which only has one record type, but it seems incredible inefficient for my app which has a dozen different record types. The recordProvider gives you a CKRecord.ID, but not the CKRecord.RecordType. Searching each record type for a matching ID seems very inefficient. Doesn't the CKSyncEngine.PendingRecordZoneChange contain an array of CKRecords, not just CKRecord.IDs? According to the documentation CKSyncEngine.RecordZoneChangeBatch has a recordsToSave property, but Xcode reports 'CKSyncEngine.PendingRecordZoneChange' has no member 'recordsToSave' I'm looking for someway to get the CKRecords from syncEngine.state.pendingRecordZoneChanges.
3
1
548
Feb ’24
How to determine model type in CKSyncEngine's nextRecordZoneChangeBatch?
I’m working on a project where I’m using CKSyncEngine to sync different types of SwiftData models, specifically User and Organization, to CloudKit. Here’s how I schedule these models to be synced: For the User model: let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [.saveRecord(user.recordID)] syncEngine.state.add(pendingRecordZoneChanges: pendingSaves) For the Organization model: let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [.saveRecord(organization.recordID)] syncEngine.state.add(pendingRecordZoneChanges: pendingSaves) The problem arises in my CKSyncEngineDelegate's nextRecordZoneChangeBatch method where from CKRecord.ID alone I need to create the actual CKRecord that will be synced to CloudKit. This recordID alone doesn’t provide enough information to determine 1) in which local model table I need to fetch actual data to build whole CKRecord; and 2) what to put in CKRecord.recordType - whether it’s a User or an Organization. Question: What is the best practice for passing or determining the model type (e.g., User or Organization) in nextRecordZoneChangeBatch? How should I handle this in a way that effectively differentiates between the different model types being synced? Any advice or examples would be greatly appreciated! Few ideas: embed the Model type in RecordID.recordName string, but this makes my recordNames longer (like resource_29af3932). fetch data by recordID in all local persistent storage, but this seems slow and there is constraint that User and Organization IDs should never be the same. introduce lookup table where from CKRecordID I can look up model type. Somehow extend CKRecordID to add model type field?
1
1
304
Aug ’24
Issues with SwiftData One-to-Many Relationships
I've been working with SwiftData and encountered a perplexing issue that I hope to get some insights on. When using a @Model that has a one-to-many relationship with another @Model, I noticed that if there are multiple class variables involved, SwiftData seems to struggle with correctly associating each variable with its corresponding data. For example, in my code, I have two models: Book and Page. The Book model has a property for a single contentPage and an optional array of pages. However, when I create a Book instance and leave the pages array as nil, iterating over pages unexpectedly returns the contentPage instead. You can check out the code for more details here. Has anyone else faced this issue or have any suggestions on how to resolve it? Any help would be greatly appreciated! I dont understand. How does using appended help here? I am not adding anything to the array. Here is the summary The following code defines two SwiftData models: Book and Page. In the Book class, there is a property contentPage of type Page, and an optional array pages that holds multiple Page instances. @Model class Book { var id = UUID() var title: String var contentPage: Page var pages: [Page]? init(id: UUID = UUID(), title: String, contentPage: Page) { self.id = id self.title = title self.contentPage = contentPage contentPage.book = self } func addPage(page: Page) { if pages == nil { pages = [] } page.book = self pages?.append(page) } } enum PageType: String, Codable { case contentsPage = "Contents" case picturePage = "Picture" case textPage = "Text" case blankPage = "Blank" } @Model class Page { var id = UUID() var pageType: PageType var pageNumber: Int var content: String var book: Book? init(id: UUID = UUID(), pageType: PageType, content: String, pageNumber: Int) { self.id = id self.pageType = pageType self.pageNumber = pageNumber self.content = content } } Observed Behavior: With the code above, I created a Book instance and populated all fields except for the pages, which was left as nil. However, when I attempt to iterate over the pages, I receive the contentPage instead. This indicates that there may be an issue with how SwiftData handles these associations. Expected behavior - when iterating over pages I should not see contentPage since it is a separate property
3
0
296
Oct ’24
Document-based SwiftData apps fail to identify a store on iPad?
When trying to run my document-based iPad app using iPadOS 18 beta and Xcode 16 beta, I get an error like the following after opening a document: Thread 1: Fatal error: Failed to identify a store that can hold instances of SwiftData._KKMDBackingData<MyProject.MyModel> from [:] In order to help track down what is going wrong, I downloaded the sample app from WWDC23 session "Build an app with SwiftData" found here: https://developer.apple.com/documentation/swiftui/building-a-document-based-app-using-swiftdata When I try to run the end-state of that sample code, I get a similar error when running the app on my iPad and creating a new deck: Thread 1: Fatal error: Failed to identify a store that can hold instances of SwiftData._KKMDBackingData<SwiftDataFlashCardSample.Card> from [:] Given that the sample project is generating the same error as my own project, is this a problem with SwiftData and document-based apps in general? Or is there a change of approach that I should try?
16
10
1.5k
Jun ’24
iOS 18.1 and SeiftData
After installing iOS 18.1 and iPados 18.1 we get a consuste failure when trying to add to our one to many data model. This was working well until we installed 18.1 when trying to add a entry to the many relationship we get this error Illegal attempt to map a relationship containing temporary objects to its identifiers.
6
4
914
Aug ’24
How to evolve from an attribute named throws?
Many years ago I put an attribute named throws in a core data entity. Now I want to extract the data and move it to a new swift data with more function. I try to rename the entity to avoid compile problems with throws being a swift keyword, now banned as a SwiftData field. I need a code path to extract from the original core data using swift. The SwiftData macros seem to choke on the throws keyword and substitute blanks. DB rename of the attribute still uses the original throws name at the code level.
2
0
229
Oct ’24
Unable to Access SwiftData Tutorial
I frequently referred to Apple’s tutorial on SwiftData in Swift, but recently I haven’t been able to access it. Is there a reason why? Create, update, and delete data - De velop in Swift Tutorials https://developer.apple.com/tutorials/develop-in-swift/create-update-and-delete-data
1
0
131
Oct ’24
Replacing Core Data with SwiftData
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
1
0
218
Oct ’24