SwiftData

RSS for tag

SwiftData is an all-new framework for managing data within your apps. Models are described using regular Swift code, without the need for custom editors.

Posts under SwiftData tag

200 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Filter a list of Item objects on a related Product Object passed as a Bindable
I'm currently developing a SwiftUI application that utilizes SwiftData for data management. I am facing a challenge when trying to filter a query. Specifically, I want to filter a list of Item objects to match a Product instance that is passed to my View. error : Instance member 'product' cannot be used on type 'MainItemListEncap'; did you mean to use a value of this type instead The view // // 311.1.1. MainRefToItem.swift // ComparePrice // // Created by Herman VAN CAUWELAERT on 11/12/2024. // import SwiftUI import SwiftData struct MainItemListEncap: View { @Bindable var product: Product @Query( filter: #Predicate { item in item.productGroup == product }, sort: [SortDescriptor(\Item.name)] ) var items: [Item] @Environment(\.modelContext) var modelContext var body: some View { ForEach(items) { item in VStack(alignment: .leading) { Text(item.name) Text(item.description) } } } } the product class. import SwiftData import Foundation @Model final class Product: CustomStringConvertible, CustomDebugStringConvertible { @Attribute(.unique) var productName: String var productDescription: String var outputCurrency: String // Gebruik een `String` als opslag voor `outputSystem` var outputSystemRawValue: String = MeasurementSystem.metric.rawValue // Computed property om `MeasurementSystem` te gebruiken var outputSystem: MeasurementSystem { get { MeasurementSystem(rawValue: outputSystemRawValue) } set { outputSystemRawValue = newValue.rawValue } } // er zijn verschillend item versies voor een product // als er een hoofdproduct gedelete wordt, dan zullen alle onderliggende items ook gedelete worden @Relationship(deleteRule: .cascade, inverse: \Item.productGroup) var refToItems = [Item]() init(productName: String = "", productDescription: String = "what is this product", outputCurrency: String = "EUR", outputSystem: MeasurementSystem = MeasurementSystem.metric) { self.productName = productName self.productDescription = productDescription self.outputCurrency = outputCurrency self.outputSystem = outputSystem } } the item class import Foundation import SwiftData @Model final class Item: CustomStringConvertible, CustomDebugStringConvertible { var timestamp: Date @Attribute(.unique) var name: String var productGroup: Product? var shop: String //TODO: becomes Shop var price: Double var currency: String var quantity: Double var unit: String //TODO: becomes Unit var isShown : Bool var minimumQuantity: String var rateStr: String { conversionRate != nil ? " (rate: \(ValueFormatter.shared.format(conversionRate!)))" : "" } init(timestamp: Date = Date() , name: String , shop: String = "Colruyt" , price:Double = 1.00 , currency: String = "EUR" , quantity: Double = 1.0 , unit: String = "g" , isShown:Bool = true , conversionRate: Decimal? = 1 , minimumQuantity: String = "1" ) { self.timestamp = timestamp self.name = name self.shop = shop self.price = price self.currency = currency self.quantity = quantity self.unit = unit self.isShown = isShown self.conversionRate = conversionRate self.minimumQuantity = minimumQuantity } }
1
0
71
22h
Use CoreData alongside SwiftData for the "Sharing" feature in the app.
Hello! 😊 I currently manage an app called MoneyKeeper that uses SwiftData for its data storage framework. Many users have requested a "sharing" feature, but unfortunately, SwiftData does not yet support this functionality, and it’s unclear when it will. 😭 Initially, I considered using CloudKit and CKSyncEngine to implement quick synchronization and sharing. However, due to the complexity of the current data model’s relationships, modeling it with CloudKit’s schema alone seemed overly complicated and likely to introduce bugs. As a result, I’ve decided to implement the sharing feature using CoreData and CloudKit. My plan to avoid conflicts with SwiftData includes: Keeping the local storage locations for SwiftData and CoreData separate. Using entirely separate CloudKit containers for SwiftData and CoreData. I believe these measures will minimize potential issues, but I’m wondering if there’s anything else I should consider. Using both SwiftData (for the personal database) and CoreData (for the shared database) feels like it could lead to significant technical debt in the future, and I anticipate encountering even more challenges during actual implementation. I’d greatly appreciate your valuable insights on this matter. 🙏 The app MoneyKeeper, currently operated using SwiftData. https://apps.apple.com/app/id6514279917
1
0
178
1w
SwiftData + CKSyncEngine
Hi, I'm building a habit tracking app for iOS and macOS. I want to use up to date technologies, so I'm using SwiftUI and SwiftData. I want to store user data locally on device and also sync data between device and iCloud server so that the user could use the app conveniently on multiple devices (iPhone, iPad, Mac). I already tried SwiftData + NSPersistentCloudKitContainer, but I need to control when to sync data, which I can't control with NSPersistentCloudKitContainer. For example, I want to upload data to server right after data is saved locally and download data from server on every app open, on pull-to-refresh etc. I also need to monitor sync progress, so I can update the UI and run code based on the progress. For example, when downloading data from server to device is in progress, show "Loading..." UI, and when downloading finishes, I want to run some app business logic code and update UI. So I'm considering switching from NSPersistentCloudKitContainer to CKSyncEngine, because it seems that with CKSyncEngine I can control when to upload and download data and also monitor the progress. My database schema (image below) has relationships - "1 to many" and "many to many" - so it's convenient to use SwiftData (and underlying CoreData). Development environment: Xcode 16.1, macOS 15.1.1 Run-time configuration: iOS 18.1.1, macOS 15.1.1 My questions: 1-Is it possible to use SwiftData for local data storage and CKSyncEngine to sync this local data storage with iCloud? 2-If yes, is there any example code to implement this? I've been studying the "CloudKit Samples: CKSyncEngine" demo app (https://github.com/apple/sample-cloudkit-sync-engine), but it uses a very primitive approach to local data storage by saving data to a JSON file on disk. It would be very helpful to have the same demo app with SwiftData implementation! 3-Also, to make sure I don't run into problems later - is it okay to fire data upload (sendChanges) and download (fetchChanges) manually with CKSyncEngine and do it often? Are there any limits how often these functions can be called to not get "blocked" by the server? 4-If it's not possible to use SwiftData for local data storage and CKSyncEngine to sync this local data storage with iCloud, then what to use for local storage instead of SwiftData to sync it with iCloud using CKSyncEngine? Maybe use SwiftData with the new DataStore protocol instead of the underlying CoreData? All information highly appreciated! Thanks, Martin
3
0
283
3d
Ongoing Issues with ModelActor in SwiftData?
After the problems with the ModelActor in iOS 18, it seemed like the ModelActor became more stable with iOS 18.1 and macOS 15.1. However, I’m still encountering many problems and crashes. I wanted to ask if these issues are related to my persistence layer architecture or if they’re still inherent to the ModelActor itself. I’ve generally followed the blog posts: https://fatbobman.com/en/posts/practical-swiftdata-building-swiftui-applications-with-modern-approaches/ and https://brightdigit.com/tutorials/swiftdata-modelactor/ and aim to achieve the following: I have a single DataProvider that holds the ModelContainer and uses it to configure and initialize a single DataHandler. These are created once at app launch and injected into the SwiftUI view hierarchy as EnvironmentObjects. Since I need to access the SwiftData models not only in SwiftUI but also indirectly in ViewModels or UIKit views, all read operations on the models should go through the DataProvider ( ModelContainrs MainContext), while all other CRUD operations are handled centrally via the single DataHandler (executed within a single ModelActor). Additionally, I want to monitor the entire container using another ModelActor, initialized in the DataProvider, which tracks changes to objects using TransactionHistory. I’ve managed to implement this to some extent, but I’m facing two main issues: ModelActor and Main Actor Requirement The ModelActor only updates SwiftUI views when initialized via the main context of the ModelContainer and therefore runs on the Main Actor. It would be ideal for this to work in the background, but the issue with the ModelActor that existed previously doesn’t seem to have been resolved in iOS 18.1/macOS 15.1—am I wrong about this? Frequent Crashes (more severe) Crashes occur, especially when multiple windows on macOS or iPadOS access the same DataHandler to update models. This often leads to crashes during read operations on models by a SwiftUI view, with logs like: Object 0x111f15480 of class _ContiguousArrayStorage deallocated with non-zero retain count 3. This object's deinit, or something called from it, may have created a strong reference to self which outlived deinit, resulting in a dangling reference. error: the replacement path doesn't exist: "/var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift" Can't show file for stack frame : <DBGLLDBStackFrame: 0x34d28e170> - stackNumber:1 - name:Tag.uuID.getter. The file path does not exist on the file system: /var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift This error usually happens when there are multiple concurrent accesses to the DataHandler/ModelActor. However, crashes also occur sporadically during frequent accesses from a single view with an error like "the replacement path doesn't exist." It also seems like having multiple ModelActors, as in this case (one for observation and one for data changes), causes interference and instability. The app appears to crash less frequently when the observer is not initialized, but I can’t verify this—it might just be a coincidence. My Question: Am I fundamentally doing something wrong with the ModelActors or the architecture of my persistence layer?
3
0
291
1w
Ongoing Issues with ModelActor in SwiftData?
After the significant issues with the ModelActor in iOS 18, it seemed like the ModelActor became more stable with iOS 18.1 and macOS 15.1. However, I’m still encountering problems and crashes. I wanted to ask if these issues are related to my persistence layer architecture or if they’re still inherent to the ModelActor itself. I’ve generally followed the blog posts: https://fatbobman.com/en/posts/practical-swiftdata-building-swiftui-applications-with-modern-approaches/ and https://brightdigit.com/tutorials/swiftdata-modelactor/ and aim to achieve the following: I have a single DataProvider that holds the ModelContainer and uses it to configure and initialize a single DataHandler. These are created once at app launch and injected into the SwiftUI view hierarchy as EnvironmentObjects. Since I need to access the SwiftData models not only in SwiftUI but also indirectly in ViewModels or UIKit views, all read operations on the models should go through the DataProvider (and its MainContext), while all other CRUD operations are handled centrally via the single DataHandler (executed within a single ModelActor). Additionally, I want to monitor the entire container using another ModelActor, initialized in the DataProvider, which tracks changes to objects using TransactionHistory. I’ve managed to implement this to some extent, but I’m facing two main issues: 1. ModelActor and Main Actor Requirement The ModelActor only updates SwiftUI views when initialized via the maincontext of the ModelContainer and therefore runs on the Main Actor. It would be ideal for this to work in the background, but the issue with the ModelActor that existed previously doesn’t seem to have been resolved in iOS 18.1/macOS 15.1—am I wrong about this? 2. Frequent Crashes (more severe) Crashes occur, especially when multiple windows on macOS or on iPad access the same DataHandler to update models. This often leads to crashes during read operations on models by a SwiftUI view (but not only), with logs like: error: the replacement path doesn't exist: "/var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift" Can't show file for stack frame : <DBGLLDBStackFrame: 0x34d28e170> - stackNumber:1 - name:Tag.uuID.getter. The file path does not exist on the file system: /var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift This error usually happens when there are multiple concurrent accesses to the DataHandler/ModelActor. However, crashes also occur sporadically during frequent accesses from a single view with an error like "the replacement path doesn't exist." It also seems like having multiple ModelActors, as in this case (one for observation and one for data changes), causes interference and instability. The app appears to crash less frequently when the observer is not initialized, but I can’t verify this—it might just be a coincidence. My Question: Am I fundamentally doing something wrong with the ModelActors or the architecture of my persistence layer?
1
0
175
2w
Correct way to remove arrays containing model objects in SwiftData
Are there any differences (either performance or memory considerations) between removing an array of model objects directly using .removeAll() vs using modelContext? Or, are they identical? Attached below is an example to better illustrate the question (i.e., First Way vs Second Way) // Model Definition @Model class GroupOfPeople { let groupName: String @Relationship(deleteRule: .cascade, inverse: \Person.group) var people: [Person] = [] init() { ... } } @Model class Person { let name: String var group: GroupOfPeople? init() { ... } } // First way struct DemoView: View { @Query private groups: [GroupOfPeople] var body: some View { List(groups) { group in DetailView(group: group) } } } struct DetailView: View { let group: GroupOfPeople var body: some View { Button("Delete All Participants") { group.people.removeAll() } } // Second way struct DemoView: View { @Query private groups: [GroupOfPeople] var body: some View { List(groups) { group in DetailView(group: group) } } } struct DetailView: View { @Environment(\.modelContext) private var context let group: GroupOfPeople var body: some View { Button("Delete All Participants") { context.delete(model: Person.self, where: #Predicate { $0.group.name == group.name }) } // assuming group names are unique. more of making a point using modelContext instead }
0
0
147
2w
SwiftData Quakes Sample app decoding errors from null magnitudes
Hi! I believe there might be a small bug in the SwiftData Quakes Sample App.^1 The Quakes app requests a JSON feed from USGS.^2 What seems to be breaking is that apparently earthquake entities from USGS can return with null magnitudes. That is throwing errors from the decoder: struct GeoFeatureCollection: Decodable { let features: [Feature] struct Feature: Decodable { let properties: Properties let geometry: Geometry struct Properties: Decodable { let mag: Double let place: String let time: Date let code: String } struct Geometry: Decodable { let coordinates: [Double] } } } which is expecting mag to not be nil. Here is my workaround: struct GeoFeatureCollection: Decodable { let features: [Feature] struct Feature: Decodable { let properties: Properties let geometry: Geometry struct Properties: Decodable { let mag: Double? let place: String let time: Date let code: String } struct Geometry: Decodable { let coordinates: [Double] } } } And then: extension Quake { /// Creates a new quake instance from a decoded feature. convenience init(from feature: GeoFeatureCollection.Feature) { self.init( code: feature.properties.code, magnitude: feature.properties.mag ?? 0.0, time: feature.properties.time, name: feature.properties.place, longitude: feature.geometry.coordinates[0], latitude: feature.geometry.coordinates[1] ) } }
1
0
203
2w
SwiftData Crash: Incorrect actor executor assumption.
I have this actor actor ConcurrentDatabase: ModelActor { nonisolated let modelExecutor: any ModelExecutor nonisolated let modelContainer: ModelContainer init(modelContainer: ModelContainer) { self.modelExecutor = DefaultSerialModelExecutor(modelContext: ModelContext(modelContainer)) self.modelContainer = modelContainer } /// Save pending changes in the model context. private func save() { if self.modelContext.hasChanges { do { try self.modelContext.save() } catch { ... } } } } I am getting a runtime crash on: try self.modelContext.save() when trying to insert something into the database and save Thread 1: Fatal error: Incorrect actor executor assumption; Expected same executor as MainActor. Can anyone explain why this is happening?
2
0
218
1w
Undo in SwiftData deletes all data at once.
When the following models in SwiftData, @Model final class UndoRedoData { var id: [Int] init(id: [Int]) { self.id = id } } I created the following code. struct ContentView: View { @ObservedObject var swiftDataViewModel = SwiftDataArrayViewModel.shared @State private var idArray: [Int] = [1,2,3,4] @State private var firstviewSwich: Bool = true @State private var twoviewSwich: Bool = false @State private var threeviewSwich: Bool = false var body: some View { VStack { if firstviewSwich == true { Button(action: addItem) { Text("1.New Item") } } if twoviewSwich == true { Button { forArrayData() } label: { Text("2.Data Road") } } if threeviewSwich == true { Button(action: undoItem) { Text("3.Undo") } } } } private func addItem() { withAnimation { let newItem = UndoRedoData(id: [1,2,3,4]) swiftDataViewModel.taskContext.insert(newItem) do { try swiftDataViewModel.taskContext.save() } catch { print(error) } swiftDataViewModel.fetchItems() firstviewSwich.toggle() twoviewSwich.toggle() } } private func forArrayData() { twoviewSwich.toggle() for data in idArray { swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager) } threeviewSwich.toggle() } private func undoItem() { swiftDataViewModel.arrayItemUndoManager.undo() threeviewSwich.toggle() firstviewSwich.toggle() } } class SwiftDataArrayViewModel: ObservableObject { static let shared = SwiftDataArrayViewModel() let modelContainer: ModelContainer @ObservationIgnored lazy var taskContext: ModelContext = { return ModelContext(modelContainer) }() @Published var arrayItems = [UndoRedoData]() @Published var arrayItemUndoManager = UndoManager() init() { let schema = Schema([UndoRedoData.self]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError(error) } fetchItems() } func fetchItems() { let fetchDescriptor = FetchDescriptor<UndoRedoData>() do { arrayItems = try taskContext.fetch(fetchDescriptor) } catch { fatalError(error) } } func idUndoCreate(id: Int, undoManager: UndoManager?) { undoManager?.registerUndo(withTarget: self) { target in target.removeID() } } func removeID() { if let firstUndoRedoData = arrayItems.first { print("Before Delete:\(firstUndoRedoData.id)") if !firstUndoRedoData.id.isEmpty { firstUndoRedoData.id.removeLast() } print("After Delete:\(firstUndoRedoData.id)") } do { try taskContext.save() } catch { print(error) } fetchItems() } } In this code, 1. Create an Item in New Item, 2. Execute Data Road and register the data in the array that is the same value as the data created in New Item in SwiftData one by one in UndoManager by for data in idArray. This is done because the data in the array and the data created by New Item in SwiftData can be known in advance. private func forArrayData() { twoviewSwich.toggle() for data in idArray { swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager) } // class SwiftDataArrayViewModel: ObservableObject func idUndoCreate(id: Int, undoManager: UndoManager?) { undoManager?.registerUndo(withTarget: self) { target in target.removeID() } } After registering in UndoManager, when Undo is executed with 3. Undo, instead of being able to perform Undo where one id is deleted each time, all the data of the id in SwiftData is deleted in a one-time Undo. I would like to be able to delete one id each time Undo is performed and restore them in sequence, but I can only delete them all once. Does this mean that such registration to UndoManager should not be done with for statements, etc.? Or is there another problem in the code? I want to make sure that one id is deleted for each Undo executed.
1
0
199
2w
SwiftData Predicates crashes when using Generic
I have been dealing with an error for almost 2 days now that caused my programme to crash on runtime with Thread 10: EXC_BAD_ACCESS (code=1, address=0x0) error , only when using Release mode. After many trial and errors and narrowing down the root problem I became suspicious to #Predicate and Generics being the root cause of the problem so I made these views to test it out. import SwiftUI import SwiftData struct DataBaseTestGeneric<Model : PersistentModel>: View { @State private var models: [Model] = [] var body: some View { viewLoader{ let reporter = Reporter() let pred = #Predicate<Model>{ model in return true } models = await reporter.fetch(pred) }content: { List{ ForEach(models){ model in Text("\(model.id)") } } } } } and a non-Generic version : import SwiftData struct DatabaseTest: View { @State private var transactions: [Transaction] = [] var body: some View { viewLoader { let reporter = Reporter() let pred = #Predicate<Transaction>{ dec in return true } let decs = await reporter.fetch(pred) transactions = decs }content:{ List{ ForEach(transactions){transaction in Text("\(transaction.id)") } } } } } to give you an insight viewLoader implentations is : struct viewLoader<Content : View>: View { var state : LoadingView.States = .loading let loadingTask : () async -> Void @State private var isLoading = true @ViewBuilder var content : Content var body: some View { if isLoading{ LoadingView(state) .task { await Task.detached(priority:.high){ await loadingTask() }.value isLoading = false } }else{ content } } } and I am accessing SwiftData using a background thread ( by implementing @ModelActor . the problem is that the code always crash on runtime when I am trying to fetch the data using reporter.fetch function. To make things even more weird I have to add the fact that Reporter class also have another function called fetchAll as follow : func fetchAll<T>(_ model : T.Type) async -> [T] where T : PersistentModel { let desc = FetchDescriptor<T>() let result = try? await context.fetch(desc) guard let result else { assertionFailure("Error fetching \(model) from context") return [] } return result } if i replace this function with reporter.fetch (which takes a predicate) the code will not crash either with or without using Generics , which brings me to the point that #Predicate is causing mayhem somehow ! PS: I am using Xcode Version 16.1 (16B40) , on macOs Sequoia Version 15.2 Beta (24C5089c)
2
0
201
2w
Automatic lightweight migrations in SwiftData VersionedSchema?
Currently, I have an Unversioned Schema, and lightweight changes are automatically applied to the Models. However, I'm planning to transition to a VersionedSchema, and I have a few questions: Do I need to write all lightweight migrations in the Migration Plan? or is it automatically applied like the Unversioned Schema? What happens if I do not create a lightweight migration? and just directly do lightweight changes to the latest VersionedSchema (example: Add a new property)?
1
1
200
2w
Infinite view loop
If I add items at the root level, this code works, but if I attempt to add a child to any item, it runs an infinite loop where it goes from the AddItemView back to the SubItemView and starts all over. I suspect it has something to do with the Predicate in SubItemView, but the debugger is crap, so I'm just guessing. Minimal code: @Model final class Item { var id: String var name: String var children: [Item] = [] @Relationship(deleteRule: .cascade) var parent: Item? init(name: String, parent: Item?, id: String = UUID().uuidString) { self.name = name self.parent = parent self.id = id } } struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query( filter: #Predicate<Item> { item in item.parent == nil }, sort: \Item.name ) public var rootItems: [Item] var body: some View { NavigationStack { List { ForEach(rootItems) { item in HStack { NavigationLink ( destination: SubItemView(parent: item)) { Text(item.name) } } } } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { NavigationLink(destination: AddItemView(itemParent: nil)) { Text("Add To Do") } } } } } } struct SubItemView: View { @Environment(\.dismiss) private var dismiss @Environment(\.modelContext) private var modelContext @State var parent: Item @State private var todo: String = "" @State var selectedDate = Date() @State var showPicker: Bool = false @Query var children: [Item] init(parent: Item) { self.parent = parent let parentID = parent.id _children = Query(filter: #Predicate<Item> { $0.parent.flatMap(\.id) == parentID && $0.parent.flatMap(\.id) != nil }, sort: \Item.name ) } var body: some View { Form { LabeledContent { TextField("Name", text: $parent.name) } label: { Text("Name:") } } Text("Parent: \(parent.name)\n") NavigationStack { Text("Child count: \(children.count)") List(children) { child in HStack { if(child.children.isEmpty) { Text(child.name) NavigationLink ( destination: SubItemView(parent: child)) { Text("").foregroundColor(.white).background(Color.blue) } .opacity(0) .background( Text("") ) } else { Text(child.name) NavigationLink(destination: SubItemView(parent: child)) { Text("") } } } } } .navigationTitle("Sub Items") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { NavigationLink(destination: AddItemView(itemParent: parent)) { Text("Add To Do") } } ToolbarItem { Button("Save") { try? modelContext.save() dismiss() } } } } } struct AddItemView: View { @Environment(\.dismiss) private var dismiss @Environment(\.modelContext) private var context @State var itemParent: Item? @State private var name = "" @State private var showWarning: Bool = false @State var child = Item(name: "", parent: nil) var body: some View { NavigationStack { Form { LabeledContent { TextField("Item", text: $name) } label: { Text("Todo:") } } .navigationTitle("Add New Item") .toolbar { Button("Save") { let tmp = Item(name: name, parent: itemParent) if(itemParent != nil) { itemParent!.children.append(tmp) } context.insert(tmp) try? context.save() dismiss() } } } } }
4
0
228
3w
How to erase all CloudKit data
I'm using SwiftData, and I'm using iCloud's CloudKit feature to back up my data. The problem here is that once you start backing up your data, you can't erase it completely. Even if the user adds 4 data and erases 4 again, I'm using about 2.5kb. I don't know how the user using the app will accept this. I'm trying to provide the user with the ability to erase data at once, what should I do??
0
0
188
3w
How can I transition to SwiftData while migrating the persistent store to an app group container with Core Data?
I have a Core Data app on the App Store that places its persistent store in the default location within the Application Support directory. At some point, I add widgets to the app while utilizing SwiftData. I create a widget extension and the app group to link both targets. I migrate my persistent store from the default location to the app group container directory. Now, the SwiftData code pointing to the shared container in the widget extension can query data. Here's the sample project for illustration up to this point. Then, I decided to get rid of the Core Data code entirely. Move everything in the main target to SwiftData. I need to keep my persistence controller object, however, because I can never assume that the entirety of my user base has migrated. I do need to ditch the Core Data model file. Otherwise, my new SwiftData models would conflict with the auto-generated ones from that file. But once I do that, my migration code breaks. I can't set up a persistent container without the model. And I need the container for migration. SwiftData has no native interface to migrate the store to a new location. There is a line in the documentation stating that the framework copies the store to an app group container automatically. But I wasn't able to verify that claim.
0
1
180
3w
SwiftData JSONDataStore with relationships
I am trying to add a custom JSON DataStore and DataStoreConfiguration for SwiftData. Apple kindly provided some sample code in the WWDC24 session, "Create a custom data store with SwiftData", and (once updated for API changes since WWDC) that works fine. However, when I try to add a relationship between two classes, it fails. Has anyone successfully made a JSONDataStore with a relationship? Here's my code; firstly the cleaned up code from the WWDC session: import SwiftData final class JSONStoreConfiguration: DataStoreConfiguration { typealias Store = JSONStore var name: String var schema: Schema? var fileURL: URL init(name: String, schema: Schema? = nil, fileURL: URL) { self.name = name self.schema = schema self.fileURL = fileURL } static func == (lhs: JSONStoreConfiguration, rhs: JSONStoreConfiguration) -> Bool { return lhs.name == rhs.name } func hash(into hasher: inout Hasher) { hasher.combine(name) } } final class JSONStore: DataStore { typealias Configuration = JSONStoreConfiguration typealias Snapshot = DefaultSnapshot var configuration: JSONStoreConfiguration var name: String var schema: Schema var identifier: String init(_ configuration: JSONStoreConfiguration, migrationPlan: (any SchemaMigrationPlan.Type)?) throws { self.configuration = configuration self.name = configuration.name self.schema = configuration.schema! self.identifier = configuration.fileURL.lastPathComponent } func save(_ request: DataStoreSaveChangesRequest<DefaultSnapshot>) throws -> DataStoreSaveChangesResult<DefaultSnapshot> { var remappedIdentifiers = [PersistentIdentifier: PersistentIdentifier]() var serializedData = try read() for snapshot in request.inserted { let permanentIdentifier = try PersistentIdentifier.identifier(for: identifier, entityName: snapshot.persistentIdentifier.entityName, primaryKey: UUID()) let permanentSnapshot = snapshot.copy(persistentIdentifier: permanentIdentifier) serializedData[permanentIdentifier] = permanentSnapshot remappedIdentifiers[snapshot.persistentIdentifier] = permanentIdentifier } for snapshot in request.updated { serializedData[snapshot.persistentIdentifier] = snapshot } for snapshot in request.deleted { serializedData[snapshot.persistentIdentifier] = nil } try write(serializedData) return DataStoreSaveChangesResult<DefaultSnapshot>(for: self.identifier, remappedIdentifiers: remappedIdentifiers) } func fetch<T>(_ request: DataStoreFetchRequest<T>) throws -> DataStoreFetchResult<T, DefaultSnapshot> where T : PersistentModel { if request.descriptor.predicate != nil { throw DataStoreError.preferInMemoryFilter } else if request.descriptor.sortBy.count > 0 { throw DataStoreError.preferInMemorySort } let objs = try read() let snapshots = objs.values.map({ $0 }) return DataStoreFetchResult(descriptor: request.descriptor, fetchedSnapshots: snapshots, relatedSnapshots: objs) } func read() throws -> [PersistentIdentifier : DefaultSnapshot] { if FileManager.default.fileExists(atPath: configuration.fileURL.path(percentEncoded: false)) { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 let data = try decoder.decode([DefaultSnapshot].self, from: try Data(contentsOf: configuration.fileURL)) var result = [PersistentIdentifier: DefaultSnapshot]() data.forEach { s in result[s.persistentIdentifier] = s } return result } else { return [:] } } func write(_ data: [PersistentIdentifier : DefaultSnapshot]) throws { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 encoder.outputFormatting = [.prettyPrinted, .sortedKeys] let jsonData = try encoder.encode(data.values.map({ $0 })) try jsonData.write(to: configuration.fileURL) } } The data model classes: import SwiftData @Model class Settings { private(set) var version = 1 @Relationship(deleteRule: .cascade) var hack: Hack? = Hack() init() { } } @Model class Hack { var foo = "Foo" var bar = 42 init() { } } Container: lazy var mainContainer: ModelContainer = { do { let url = // URL to file let configuration = JSONStoreConfiguration(name: "Settings", schema: Schema([Settings.self, Hack.self]), fileURL: url) return try ModelContainer(for: Settings.self, Hack.self, configurations: configuration) } catch { fatalError("Container error: \(error.localizedDescription)") } }() Load function, that saves a new Settings JSON file if there isn't an existing one: @MainActor func loadSettings() { let mainContext = mainContainer.mainContext let descriptor = FetchDescriptor<Settings>() let settingsArray = try? mainContext.fetch(descriptor) print("\(settingsArray?.count ?? 0) settings found") if let settingsArray, let settings = settingsArray.last { print("Loaded") } else { let settings = Settings() mainContext.insert(settings) do { try mainContext.save() } catch { print("Error saving settings: \(error)") } } } The save operation creates a JSON file, which while it isn't a format I would choose, is acceptable, though I notice that the "hack" property (the relationship) doesn't have the correct identifier. When I run the app again to load the data, I get an error (that there wasn't room to include in this post). Even if I change Apple's code to not assign a new identifier, so the relationship property and its pointee have the same identifier, it still doesn't load. Am I doing something obviously wrong, or are relationships not supported in custom data stores?
1
0
184
3w
Linked list?
The code below works, in that it successfully creates an item and attached it to the parent item, but the parent view doesn't show the new child until you navigate away from the parent and then back to it. Parent: Child1 Child2 // Add Child 3 and this does not refresh when returning through the dismiss() call, but if I navigate to the grand Parent, and then back to the Parent, Child 3 is there. Any ideas? NavigationStack { Form { LabeledContent { TextField("Item", text: $name) } label: { Text("Item:") } } .navigationTitle("Add New Item") .toolbar { Button("Save") { var tmp = Item(timestamp: Date(), name: name, parent: itemParent) if(itemParent != nil) { itemParent!.children.append(tmp) } context.insert(tmp) try? context.save() dismiss() } } }
2
0
280
3w
CrashReportError: Fatal Error in PersistentModel.swift
I got a preview crash info as this: *** crashed due to fatalError in PersistentModel.swift at line 559. This KeyPath does not appear to relate RecordInfo to anything - \RecordInfo.<computed 0x00000001921dfb4e (Array<SomeInfo>)> If I change preview device to iPhoneSE, It is no crash. I think the reason is I have some dirty data in iPhone16 Pro preview device. If I'm right and how to delete it? Model of RecordInfo like this: @Model class RecordInfo { @Relationship(deleteRule: .cascade) var someInfo:[SomeInfo] } @Model class SomeInfo { var date: Date var info: String }
1
0
218
3w
Does SwiftData copy the Core Data store to the app group container automatically?
While reading the developer documentation article Adopting SwiftData for a Core Data App, one particular line piqued my interest. For apps that evolve from a version that doesn’t have any app group container to a version that has one, SwiftData copies the existing store to the app group container. Given how troublesome it has been to migrate the Core Data persistent store to an app group container, I decided to try this out myself. I created an Xcode project using the default Core Data template. I then added a few Item objects with timestamps. There, I had what we would consider a regular Core Data app. I then created a widget extension for this app since this is one of the most common uses for adopting an app group in an Xcode project. After that, I linked the main target with the widget extension using an app group. In the widget extension, I tried to fetch the Item objects. I utilized the SwiftData code in the sample project associated with the article above. struct Provider: TimelineProvider { private let modelContainer: ModelContainer init() { let appGroupContainerID = "group.com.genebogdanovich.CoreDataSwiftDataAppGroup" guard let appGroupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupContainerID) else { fatalError("Shared file container could not be created.") } let url = appGroupContainer.appendingPathComponent("CoreDataSwiftDataAppGroup.sqlite") print("\(url)") do { modelContainer = try ModelContainer(for: Item.self, configurations: ModelConfiguration(url: url)) } catch { fatalError("Failed to create the model container: \(error)") } } } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { Task { @MainActor in let fetchDescriptor = FetchDescriptor<Item>() let items: [Item] = try! modelContainer.mainContext.fetch(fetchDescriptor) print(items) let entry = SimpleEntry(date: .now, emoji: "😀", count: items.count) let timeline = Timeline(entries: [entry], policy: .never) completion(timeline) } } The fetch yielded no results. However, as I explored the app group directory in the file system, I found a .sqlite file. That is interesting because SwiftData creates .store files by default. So, I am guessing that SwiftData did copy something. Or the ModelContainer initializer just created another empty SQLite file since the fetch returned zero results. I would highly appreciate someone elaborating on that quote from the documentation.
0
2
163
3w