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

View is not updating by adding or deleting an item
When I parse the query result(SwiftData) to my DetailView it works fine. But when I delete the item in the DetailView the view isn't updated. I parse the array from the dataModel to the DetailView. But I do not understand why it's not updating when I add or delete a tree in the DetailView. Only when I append the tree to the array from the other model after insert it works. Does someone have a tip for me? Here is my Code: https://gist.github.com/romanindermuehle/14441c21f689e91b26942d997f75300d
0
0
352
May ’24
Memory usage unstoppably increasing when updating SwiftData Object from Timer
For a kind of podcast player I need to periodically update a swiftData object to keep track of the listening progress. (Happy to hear if there are better ways) I need to do this in many places in my app so I wanted to extract the modelContext into a Singleton so I can write a global function that starts the timer. In doing so I stumbled upon a problem: The memory used by my app is steadily increasing and the device is turning hot. @Observable class Helper { static let shared = Helper() var modelContext: ModelContext? } @main struct SingletontestApp: App { let modelContainer: ModelContainer init() { do { modelContainer = try ModelContainer( for: Item.self, Item.self ) } catch { fatalError("Could not initialize ModelContainer") } Helper.shared.modelContext = modelContainer.mainContext } var body: some Scene { WindowGroup { ContentView() } .modelContainer(modelContainer) } } struct ContentView: View { @Query private var items: [Item] var body: some View { NavigationSplitView { List { ForEach(items) { item in Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } .toolbar { ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } ToolbarItem { Button(action: updateItemPeriodically) { Label("Change random", systemImage: "dice") } } } } detail: { Text("Select an item") } } func addItem() { withAnimation { let newItem = Item(timestamp: Date()) Helper.shared.modelContext!.insert(newItem) } } @MainActor func updateItemPeriodically() { // Doesn't matter if run as global or local func let descriptor = FetchDescriptor<Item>(sortBy: [SortDescriptor(\.timestamp)]) let results = (try? Helper.shared.modelContext?.fetch(descriptor)) ?? [] let element = results.randomElement() let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in // Smaller time intervals worsen the problem element?.timestamp = Date.now } } } Calling save() manually or automatically in the timer does not have any effect. I am not sure about my general way of keeping track of listening process so if you think there is a better way, feel free to correct me. Thanks for your help
3
0
472
May ’24
SwiftData does not retrieve my inverse one-to-many Relationship
Here is my models: import SwiftData @Model final public class FirstModel { let name: String @Relationship(deleteRule: .cascade, inverse: \SecondModel.parent) var children = [SecondModel]() init(name: String) { self.name = name } } @Model final public class SecondModel { let parent: FirstModel let name: String @Relationship(deleteRule: .cascade, inverse: \ThirdModel.parent) var children = [ThirdModel]() init(name: String, parent: FirstModel) { self.name = name self.parent = parent } } @Model final public class ThirdModel { let parent: SecondModel let name: String init(name: String, parent: SecondModel) { self.name = name self.parent = parent } } Then I create my model entries: let schema = Schema([ FirstModel.self, SecondModel.self, ThirdModel.self ]) let container = try ModelContainer(for: schema) let context = ModelContext(container) let firstModel = FirstModel(name: "my first model") let secondModel = SecondModel(name: "my second model", parent: firstModel) let thirdModel = ThirdModel(name: "my third model", parent: secondModel) context.insert(firstModel) context.insert(secondModel) context.insert(thirdModel) try context.save() I want to retrieve the children from my models: print("-- Fetch Third Model") let thirdFetchDescriptor: FetchDescriptor<ThirdModel> = FetchDescriptor<ThirdModel>(predicate: #Predicate { $0.name == "my third model" }) let thirdModels = try context.fetch(thirdFetchDescriptor) for entry in thirdModels { print(">>> \(entry) - \(entry.parent) - \(entry.parent.parent)") } print("-- Fetch First Model") let firstFetchDescriptor: FetchDescriptor<FirstModel> = FetchDescriptor<FirstModel>(predicate: #Predicate { $0.name == "my first model" }) let firstModels = try context.fetch(firstFetchDescriptor) for entry in firstModels { print(">>> \(entry) - \(entry.children)") for child in entry.children { print("\t>>> \(child) - \(child.children)") } } ... But it does not seem to work: -- Fetch Third Model >>> cardapart_sdk_app_ui.ThirdModel - cardapart_sdk_app_ui.SecondModel - cardapart_sdk_app_ui.FirstModel -- Fetch First Model >>> cardapart_sdk_app_ui.FirstModel - [] What I would expect to see: -- Fetch First Model >>> cardapart_sdk_app_ui.FirstModel - [cardapart_sdk_app_ui.SecondModel] >>> cardapart_sdk_app_ui.SecondModel - [cardapart_sdk_app_ui.ThirdModel] I am not sure what I am doing wrong or missing...
1
0
515
May ’24
CloudKit data is lost when reinstalling the app
We worked with SwiftData, and once CloudKit was integrated, the synchronization worked well. Even if I rerun the app, it works just as well. However, when I delete the app and reinstall it, I get a Token Expired error and CloudKit doesn't work properly. My code is organized like this public lazy var modelContext: ModelContext = { ModelContext(modelContainer) }() private lazy var modelContainer: ModelContainer = { let schema = Schema([ Entity1.self, Entity2.self, Entity3.self, ]) let modelConfiguration = ModelConfiguration( schema: schema, groupContainer: .identifier("myGroupContainer"), cloudKitDatabase: .automatic ) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() The error content is as follows error: CoreData+CloudKit: -[PFCloudKitImportRecordsWorkItem fetchOperationFinishedWithError:completion:]_block_invoke(707): <PFCloudKitImporterZoneChangedWorkItem: 0x3022c0000 - <NSCloudKitMirroringImportRequest: 0x3036e7ac0> 1A7E53D4-E95B-423F-8887-66360F6D8865> { ( "<CKRecordZoneID: 0x301bb1bf0; zoneName=com.apple.coredata.cloudkit.zone, ownerName=__defaultOwner__>" ) } - Fetch finished with error: <CKError 0x301bb5650: "Partial Failure" (2/1011); "Couldn't fetch some items when fetching changes"; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A; container ID = "MyContainerID"; partial errors: { com.apple.coredata.cloudkit.zone:__defaultOwner__ = <CKError 0x301bb1830: "Change Token Expired" (21/2026); server message = "client knowledge differs from server knowledge"; op = 515034AC3ADC4348; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A> }> error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _importFinishedWithResult:importer:](1390): <PFCloudKitImporter: 0x3000a1240>: Import failed with error: <CKError 0x301bb5650: "Partial Failure" (2/1011); "Couldn't fetch some items when fetching changes"; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A; container ID = "MyContainerID"; partial errors: { com.apple.coredata.cloudkit.zone:__defaultOwner__ = <CKError 0x301bb1830: "Change Token Expired" (21/2026); server message = "client knowledge differs from server knowledge"; op = 515034AC3ADC4348; uuid = 3F346302-C3EE-4F72-820C-988287C92C0A> }> Forcing the ModelContainer to be reinitialized fixes the problem, it's a problem to get this error in the first place, the error doesn't even go to fatal for me, so I don't even know how to verify that it's happening. Is there something I'm doing wrong, or do you have any good ideas for solving the same problem?
2
0
489
May ’24
I wonder swiftdata query cannot be used within a class
import SwiftUI import SwiftData class DateManagerStore : ObservableObject { @Query private var myData: [myData] @Published var myDataToString = "" func hopitalDataQuery() { if let lastMyData = myData { self.myDataToString = String(lastMyData.sorted(by: {$0.visitedDate > $1.visitedDate}).last) } } } struct MainView: View { @EnvironmentObject var dateManagerStore : DateManagerStore var body: some View { VStack{ Text("\(dateManagerStore.myDataToString)") } .onAppear(perform: { dateManagerStore.hopitalDataQuery() }) } } I thought it would be good to manage SwiftData values ​​used within multiple views in one place. I wanted to use Query data in the DateManagerStore class declared as ObservableObject through onApper of the MainView. However, when printing the myData variable within hopitalDataQuery() of the DateManagerStore class, empty data was output. I tried to use @Query defined inside the DateManagerStore class in various ways, but none of the methods allowed me to put a value into the @Query variable 'myData'. There is no error in Xcode itself, but no data is coming in. I can't find any related information anywhere, so I ask if it's officially not possible.
2
1
604
May ’24
Maintaining a local copy of server data - question about the sample code
I've been going through SwiftData documentation, as I'm trying to learn how to work with SwiftData, especially how to use it to cache web responses. "Maintaining a local copy of server data" sample seems to be perfect source for me then, but I find it a little bit confusing, or lacking. There's this function: /// Loads new earthquakes and deletes outdated ones. @MainActor static func refresh(modelContext: ModelContext) async { do { // Fetch the latest set of quakes from the server. logger.debug("Refreshing the data store...") let featureCollection = try await fetchFeatures() logger.debug("Loaded feature collection:\n\(featureCollection)") // Add the content to the data store. for feature in featureCollection.features { let quake = Quake(from: feature) // Ignore anything with a magnitude of zero or less. if quake.magnitude > 0 { logger.debug("Inserting \(quake)") modelContext.insert(quake) } } logger.debug("Refresh complete.") } catch let error { logger.error("\(error.localizedDescription)") } } It says specifically that it "deletes outdated ones", but where does the actual deletion happens? All I can see is call to modelContext.insert(quake) which will update existing entries or create new ones if they don't exist yet, if I'm understanding things right. But quakes that were already in the database, and are not present in the response, don't seem to be deleted anywhere in this function. Or am I missing something?
1
0
276
May ’24
Swiftdata Rollingback not triggering view update
I have a view which is a form allowing me to edit details of a category item. In the toolbar, there is a button called "Cancel," whose sole function is to call the rollback function in the ModelContext class. This function should allow you to revert any changes made to the context. However, it appears that this rollback function is neither triggering the view update nor working properly, as I need to restart the app to see the changes reverted which just work when the property "isAutosaveEnabled" is set to false. Container configuration goes below import SwiftUI @main struct Index: App { var body: some Scene { WindowGroup { Window() } .modelContainer( for: [ Bill.self, Category.self ], isAutosaveEnabled: false, isUndoEnabled: true ) } } Rollback usage that don't work import SwiftUI import SwiftData import Foundation struct CategoryForm: View { @Environment(\.presentationMode) private var presentationMode @Environment(\.modelContext) private var modelContext @Bindable var category: Category var body: some View { NavigationStack { Form { Section( header: Text("Basic Information"), content: { TextField("Title", text: $category.name) } ) } .navigationTitle("New Category") .navigationBarTitleDisplayMode(.inline) .toolbar { // Cancel Button ToolbarItem( placement: .topBarLeading, content: { Button( action: onCancel, label: { Text("Cancel") } ) } ) // Done Button ToolbarItem( placement: .topBarTrailing, content: { Button( action: onDone, label: { Text("Done") } ) .disabled(category.name.isEmpty) } ) } } } func onCancel() { modelContext.rollback() presentationMode.wrappedValue.dismiss() } func onDone() { modelContext.insert(category) do { try modelContext.save() } catch { fatalError("Failed to save category") } presentationMode.wrappedValue.dismiss() } }
0
0
256
May ’24
Swift Data crashing on close document
Hello I have a swiftUI/swiftData document based app. In this I have created a singleton object using the folllowing in my ContentView struct: @Query private var persistantStores:[PersistantStateManagerStorage] @State private var stateManager: PersistantStateManagerStorage? I then, call the following on the onAppear and onDisappear calls on the top level HStack{} item in my view .onAppear(){ if let store = persistantStores.first { self.stateManager = store // data.first } else { self.stateManager = PersistantStateManagerStorage() modelContext.insert(stateManager!) try? modelContext.save() } }.onDisappear(){ print("bye bye") self.stateManager = nil } I then wrap my view inside a if let stateManager = stateManager {} block to unwrap the optional, and bind to values with a call such as Stepper("Nudge Amount", value: Bindable(stateManager).nudgeAmount, in: 1...20) I have to use Bindable(stateManager) rather than $stateManager as the $stateManager doesn't know its been unwrapped All of this works fine until I close a document, at which point I get a crash with the message "SwiftData/BackingData.swift:124: Fatal error: Unable to get value - no backing Managed Object" which appears to happen when trying to access some of the properties of my persistantStore object (which has been retired) I assume that my persistantStore object being an optional is part of the problem but I can't work out a better method of doing what I am trying to do
3
0
409
Jun ’24
Store USDZ with SwiftData
I am trying to store usdz files with SwiftData for now. I am converting usdz to data, then storing it with SwiftData My model import Foundation import SwiftData import SwiftUI @Model class Item { var name: String @Attribute(.externalStorage) var usdz: Data? = nil var id: String init(name: String, usdz: Data? = nil) { self.id = UUID().uuidString self.name = name self.usdz = usdz } } My function to convert usdz to data. I am currently a local usdz just to test if it is going to work. func usdzData() -> Data? { do { guard let usdzURL = Bundle.main.url(forResource: "tv_retro", withExtension: "usdz") else { fatalError("Unable to find USDZ file in the bundle.") } let usdzData = try Data(contentsOf: usdzURL) return usdzData } catch { print("Error loading USDZ file: \(error)") } return nil } Loading the items @Query private var items: [Item] ... var body: some View { ... ForEach(items) { item in HStack { Model3D(?????) { model in model .resizable() .scaledToFit() } placeholder: { ProgressView() } } } ... } How can I load the Model3D? I have tried: Model3D(data: item.usdz) Gives me the errors: Cannot convert value of type '[Item]' to expected argument type 'Binding<C>' Generic parameter 'C' could not be inferred Both errors are giving in the ForEach. I am able to print the content inside item: ForEach(items) { item in HStack { Text("\(item.name)") Text("\(item.usdz)") } } This above works fine for me. The item.usdz prints something like Optional(10954341 bytes) I would like to know 2 things: Is this the correct way to save usdz files into SwiftData? Or should I use FileManager? If so, how should I do that? Also how can I get the usdz from the storage (SwiftData) to my code and use it into Model3D?
3
0
572
May ’24
SwiftData Issue: Unable to Retrieve Data with @Query After Background Wake-up
Recently, I have been using SwiftData as a data persistence tool in my new SwiftUI app. My app utilizes CLLocationManager for background location tracking to wake up the app and update SwiftData-related data. When the app is in the foreground or background, SwiftData's @Query can normally retrieve data. However, once the user manually terminates the app and it receives a location-based wake-up, SwiftData's @Query no longer retrieves data. I have looked through many resources and haven't found any similar documentation. I wonder if anyone can give me some suggestions?
0
0
246
May ’24
SwiftData relationships
I am new to Swift, and I am trying to get a grip on SwiftData, which SO FAR seems pretty straightforward EXCEPT relationships between tables. I come from a MYSQL, SQLITE3 background and I am used to “Primary Keys”, Inner and Outer Joins, One-to-One and One-to-Many relationships. Which SwiftData does not appear to do (I think). I am following several reasonably good online tutorials on the subject, all of which gloss over how relationships work. The best one is a table for vegetables alongside a table of notes for each vegetable (A one-to-many relationship). The classes we set up for the schemas are as follows. import Foundation import SwiftData @Model class Vegetable { var name: String @Relationship(deleteRule: .cascade) var notes: [Note]? init(name: String) { self.name = name } } @Model class Note { var text: String var vegetable: Vegetable? init(text: String) { self.text = text } } I can’t comprehend how they work. None of the tutorials I have watched explain it or explain it in a way I can understand. In the “Vegetable” class, why is the field “notes” an array of type Notes? Again in the “Vegetable” class does the field “notes” get stored in the database, if so what is stored? In the “Note” Class it looks like the whole of the class “Vegetable” gets stored in the variable “vegetable”, which may or may not get stored in the database.
1
0
596
May ’24
Spurious View invalidation with NavigationStack and @Query with a predicate
I've run into a problem related to navigation links in child Views containing a SwiftData @Query and a predicate. When tapping on a NavigationLinks, the containing View is invalidated pausing the UI. When tapping back, the View is invalidated a second time during which time the View ignores any new taps for navigation leading to a poor user experience. A complete example: import SwiftUI import SwiftData @Model final class Item { var num: Int init(num: Int) { self.num = num } } @main struct TestSwiftDataApp: App { var sharedModelContainer: ModelContainer = { let schema = Schema([Item.self]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) let container: ModelContainer do { container = try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } // Add some sample data Task { @MainActor in for i in 0...1000 { container.mainContext.insert(Item(num: i)) } } return container }() var body: some Scene { WindowGroup { ContentView() } .modelContainer(sharedModelContainer) } } extension Color { static func random() -> Color { Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1)) } } struct ContentView: View { var body: some View { NavigationStack { SubView() .navigationDestination(for: Item.self) { item in Text("Item at \(item.num)") } } } } struct SubView: View { @Environment(\.modelContext) private var modelContext @Query(filter: #Predicate<Item> { item in item.num < 20 }, sort: \.num) private var items: [Item] var body: some View { let _ = Self._printChanges() List { ForEach(items) { item in NavigationLink(value: item) { Text("Item \(item.num)") }.background(Color.random()) } } } } The background colors of cells will shift every invalidation. In addition there's some debugging in there to show what's happening. When running it, I get SubView: @self, @identity, _modelContext, @128, @144 changed. SubView: @self changed. SubView: @dependencies changed. Then I tap on an item and it invalidates: SubView: @self changed. Tapping back invalidates it again during which time the UI ignores new taps: SubView: @self changed. The odd thing is, this behavior doesn't happen if the NavigationStack is moved to the child View with the NavigationLinks like this: struct ContentView2: View { var body: some View { SubView2() } } struct SubView2: View { @Environment(\.modelContext) private var modelContext @Query(filter: #Predicate<Item> { item in item.num < 20 }, sort: \.num) private var items: [Item] var body: some View { let _ = Self._printChanges() NavigationStack { List { ForEach(items) { item in NavigationLink(value: item) { Text("Item \(item.num)") }.background(Color.random()) } } .navigationDestination(for: Item.self) { item in Text("Item at \(item.num)") } } } } When running this, there's one less change as well and no invalidations on tap or back: SubView: @self, @identity, _modelContext, @128, @144 changed. SubView: @dependencies changed. The problem also doesn't happen if the @Query does not have a filter #Predicate. Unfortunately, the application in question has a deeper hierarchy where views with a @Query with a predicate can navigation to other views with a @Query and predicate, so neither solution seems ideal. Is there some other way to stop the invalidations from happening?
1
0
345
May ’24
Can SwiftData @Model with .externalStorage be used with CloudKit?
Hi, I'm about to adopt .externalStorage but I will also use CloudKit in the near future. I did not find any reference that list of requirements for SwiftData models to be used with CloudKit, only for Core Data models. It seems those Core Data requirements (like no-unique) apply to SwiftData as well. However I did not find any info on this: Can @Attribute(.externalStorage) be used when I want to sync my model with CloudKit?
2
0
382
May ’24
How does SwiftData and Decimal Works?
Hi! Not sure if this is a swift data or more a Decimal in general type of question. What's going on: I have a SwiftUI app using SwiftData, I have persisted a Model with a property "reducedPrice" of type Decimal. It's stores correctly the value. Now, I have read the value during automated tests and tried comparing the values: let reducedPrice = model.reducedPrice // swift data property let target = Decimal(4.98) // expected target value to compare to swift data value. Now if I just print the result of the comparison between those 2 I get a false result. print(reducedPrice == target) //output : false The swift data model was populated from a direct copy of another struct that comes from an JSON import using Codable+CodingKeys (I used Decimal type). What I expected: I expected it to be true. Debug Observations I did noticed that on the variable inspector both had the same magnitudes but in reality the mantissa are different. I'm attaching a screenshot. My Theory They are different just because something under SwiftData stores different way the decimal as in comparison on how I am creating the Decimal for the comparison inside the automated tests. My question Is this expected behavior? Any suggestion on best practices on how to handle this? Thank you in advance any relevant guidance is very appreciated!
2
1
385
May ’24
SwiftUI App Crashes on Simulator When Using @Query Macro in Chart Interaction
I'm developing an app with a chart in SwiftUI. I want the following block of code to run when the chart is clicked. On my personal iPhone, the app works flawlessly. But when I try it in the simulator it crashes and gives me about 5-10 of the following errors. When I remove the @Query macro from the code block, the application does not crash in the simulator, but I continue to get the errors I mentioned. If I do not run the following code block, I do not get the errors I mentioned. struct SaleDetailView: View { @Query(filter: #Predicate<Registration> { !$0.activeRegistration }) private var regs: [Registration] var body: some View { VStack { DailySaleView() } .padding() } } Thank you in advance for your answers. Do not hesitate to ask if you have any questions. Thanks, MFS
1
0
296
May ’24
SwiftData causing app to hang
UPDATED: I determined the line causing the hang was .animation(.default, value: layout). I'm seeing a strange issue when switching between a ScrollView/LazyVGrid and a List with my SwiftData but when toggling the layout it ends up freezing and I can't confirm what's causing the app to hang since there's no crash. I'm not getting much info from the debugger. Any help would be appreciated. struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var items: [Item] let gridItemLayout = [ GridItem(.adaptive(minimum: 150))] @State private var layout = Layout.grid var body: some View { NavigationStack { ZStack { if layout == .grid { ScrollView { LazyVGrid(columns: gridItemLayout, spacing: 5) { ForEach(items) { item in } } } } else { List { ForEach(items) { item in } } } } // MARK: HERE'S THE ERROR .animation(.default, value: layout) .navigationTitle("ScrollView") .toolbar { ToolbarItemGroup { Button(action: addItem) { Label("Add Item", systemImage: "plus") } Menu { Picker("Layout", selection: $layout) { ForEach(Layout.allCases) { option in Label(option.title, systemImage: option.imageName) .tag(option) } } .pickerStyle(.inline) } label: { Label("Layout Options", systemImage: layout.imageName) .labelStyle(.iconOnly) } } } } } } @Model public class Item: Codable { public let id: String public enum CodingKeys: String, CodingKey { case id } public init(id: String) { self.id = id } required public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) } }
0
0
315
May ’24
Preview crashed because of a Fatal Error in ModelData.
Can anyone give me assistance on how to fix this. My preview crashed because a Fatal Error in ModelData. This is how my ModelData looks: import SwiftUI struct SutraokeDetail: View { @Environment(ModelData.self) var modelData var sutra: Sutras var body: some View { @Bindable var modelData = modelData ScrollView { CircleImage(image: sutra.image) .offset(y: -130) .padding(.bottom, -130) VStack(alignment: .leading) { HStack{ Text(sutra.name) .font(.title) Spacer() Text(sutra.text) } } } } } #Preview { let modelData = ModelData() return SutraokeDetail(sutra: modelData.sutras[0]) .environment(modelData) }
3
0
433
May ’24
How do I resolve conflicts with SwiftData?
SwiftData includes support for CloudKit sync. However, I don't see any way to add conflict resolution behavior. For example, if different devices set different values for a field, or if a relationship is orphaned because of a deletion on another device, the application has to handle this somehow. In Core Data (which SwiftData wraps), you can handle this with the conflict resolution system (docs) and classes like NSMergePolicy. Is any of this accessible in SwiftData? If not, how do you deal with conflicts when syncing a SwiftData model with the cloud?
2
1
593
May ’24