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.

SwiftData Documentation

Posts under SwiftData tag

492 Posts
Sort by:
Post marked as solved
2 Replies
162 Views
The deletion is working, but it does not refresh the view. This is similar to a question I asked previously but I started a new test project to try and work this out. @Model class Transaction { var timestamp: Date var note: String @Relationship(deleteRule: .cascade) var items: [Item]? init(timestamp: Date, note: String, items: [Item]? = nil) { self.timestamp = timestamp self.note = note self.items = items } func getModifierCount() -> Int { guard let items = items else { return 0 } return items.reduce(0, {result, item in result + (item.modifiers?.count ?? 0) }) } } @Model class Item { var timestamp: Date var note: String @Relationship(deleteRule: .nullify) var transaction: Transaction? @Relationship(deleteRule: .noAction) var modifiers: [Modifier]? init(timestamp: Date, note: String, transaction: Transaction? = nil, modifiers: [Modifier]? = nil) { self.timestamp = timestamp self.note = note self.transaction = transaction self.modifiers = modifiers } } @Model class Modifier { var timestamp: Date var value: Double @Relationship(deleteRule: .nullify) var items: [Item]? init(timestamp: Date, value: Double, items: [Item]? = nil) { self.timestamp = timestamp self.value = value self.items = items } } struct ContentView: View { @Environment(\.modelContext) private var context @Query private var items: [Item] @Query private var transactions: [Transaction] @Query private var modifiers: [Modifier] @State private var addItem = false @State private var addTransaction = false var body: some View { NavigationStack { List { Section(content: { ForEach(items) { item in LabeledText(label: item.timestamp.formatAsString(), value: .int(item.modifiers?.count ?? -1)) } .onDelete(perform: { indexSet in withAnimation { for index in indexSet { context.delete(items[index]) } } }) }, header: { LabeledView(label: "Items", view: { Button("", systemImage: "plus", action: {}) }) }) Section(content: { ForEach(modifiers) { modifier in LabeledText(label: modifier.timestamp.formatAsString(), value: .currency(modifier.value)) } .onDelete(perform: { indexSet in indexSet.forEach { index in context.delete(modifiers[index]) } }) }, header: { LabeledView(label: "Modifiers", view: { Button("", systemImage: "plus", action: {}) }) }) Section(content: { ForEach(transactions) { transaction in LabeledText(label: transaction.note, value: .int(transaction.getModifierCount())) } .onDelete(perform: { indexSet in withAnimation { for index in indexSet { context.delete(transactions[index]) } } }) }, header: { LabeledView(label: "Transactions", view: { Button("", systemImage: "plus", action: {addTransaction.toggle()}) }) }) } .navigationTitle("Testing") .sheet(isPresented: $addTransaction, content: { TransactionEditor() }) } } } } Here's the scenario. Create a transaction with 1 item. That item will contain 1 modifier. ContentView will display Items, Modifiers, and Transactions. For Item, it will display the date and how many modifiers it has. Modifier will display the date and its value. Transactions will display a date and how many modifiers are contained inside of its items. When I delete a modifier, in this case the only one that exist, I should see the count update to 0 for both the Item and the Transaction. This is not happening unless I close the application and reopen it. If I do that, it's updated to 0. I tried to add an ID variable to the view and change it to force a refresh, but it's not updating. This issue also seems to be only with this many to many relationship. Previously, I only had the Transaction and Item models. Deleting an Item would correctly update Transaction, but that was a one to many relationship. I would like for Modifier to have a many to many relationship with Items, so they can be reused. Why is deleting a modifier not updating the items correctly? Why is this not refreshing the view? How can I resolve this issue?
Posted
by Xavier-k.
Last updated
.
Post not yet marked as solved
0 Replies
118 Views
I have a TabView which consists of a few different tabs. One of which does an @Query to retrieve an array of Transaction models. These are then displayed in a list using a ForEach. struct TransactionsTab: View { @Query private var transactions: [Transaction] ... other code Section(content: { ForEach(transactions) { transaction in transaction.getListItem() } }, header: { LabeledView(label: "Recent Transactions", view: { ListButton(mode: .link(destination: { ListView(list: transactions) .navigationTitle("All Transactions") })) }) }) Transaction contains a different model called TransactionItem and that has a variable called amount. That amount variable is used in the getListItem() function to show how much the total transaction was in the list item. The issue is that I can delete a Transaction and the ForEach will update to reflect that. However, if I delete an TransactionItem separately, that getListItem() will not show that it's been deleted. The total amount shown will still be as if the TransactionItem was never deleted. It will only update when the app is closed and reopened. Below is the code that's ran when deleting a model, in this case a TransactionItem. // Deletes a single item private func delete() { deleteWarning = false if let item = itemToDelete { // If last item is being delete, dismiss the view if list.count == 1 { dismissView() } context.delete(item) context.saveContext() itemToDelete = nil } mode = .view } I would think that deleting the model and having it save will cause the transaction query to update. What's going on here to cause it to not update? By the way, saveContext() just calls the ModelContext save function. extension ModelContext { func saveContext() { do { try self.save() } catch { print("Could not save context: \(error.localizedDescription)") } } }
Posted
by Xavier-k.
Last updated
.
Post marked as solved
3 Replies
231 Views
I have a driving tracking app I'm working on that properly starts tracking and logging location when the app is in the foreground or background. I use a buffer/queue to keep recent locations so when a trip ramps up to driving speed I can record that to work back to the start location just before the trip starts. This works great, however, in background mode when the user does not have the app open it will record locations but not until a significant location change is detected. The buffering I do is lost and the location only starts tracking several hundred yards or more after the trip has started. Does anyone have any suggestions or strategies to handle this chicken and the egg scenario?
Posted Last updated
.
Post not yet marked as solved
0 Replies
136 Views
Hello, I developed an application that uses iBeacons to create events, however when the app is in the terminated state I notice that part of my code is activated but the rest of the process is not activated ex : ranging beacon and notification. Is there a way to completely wake up my app when it is in terminated state or at least send a notification to the user to inform them that the app must be opened to put it back in background state so that the app working correctly?
Posted
by JMG22.
Last updated
.
Post not yet marked as solved
1 Replies
254 Views
Looking at having editable text as the detail view within a master detail interface. There are various examples out there that show how to build the NSViewRepresentable wrapper around NSTextView. e.g. https://developer.apple.com/forums/thread/125920 However, when I try to embed the NSViewRepresentable within a NavigationSplitView the various examples (and my own attempt) don't work properly. There seems to be a race condition competing to update the internal bindings. I also see updateNSView being called multiple times when only one event, say selection change, is called once. Using the SwiftUI TextEditor view actually works. (it is just so limited) When the master selected item is changed the change isn't automatically propagated through to update the Coordinator. That's because there isn't a delegate for "master did change". Take any of the TextViews on their own (not within a split view) and they work as you would expect. The SwiftUI TextEditor does this without any obvious connection to the master. So the first question is: has anybody solved this type of problem? I haven't yet found a link to the solution or a discussion on the internal trick to make this work. In principle the view structure is: NavigationSplitView { // master list List(selection : $selectedItem { } } content: { TextEditor(text: $selectedItem.text) // works // or CustomTextView(text: $selectedItem.text) // all examples I've found so far fail to properly display changes to the text when edited } My current thought is that the internal Coordinator needs to know that there has been a selection change so you can synchronise the significant change in the contents of the text binding. Again TextEditor doesn't need to know that. makeNSView is only called once, so there isn't any regeneration of the NSTextView that you could rely on. Have tried on both macOS and iOS and see the same results.
Posted
by purple.
Last updated
.
Post not yet marked as solved
4 Replies
594 Views
This possibly seems like a regression but from iOS 17.1.0+, I'm having issues from Xcode 15.2 Beta &where when using a transformable property I'm getting a crash when trying to create a model container. This worked fine for me in Xcode 15.1 Beta when testing on iOS OS 17.0.1 and below. I have a simple model where I'm trying to save a UIColor, below is an example of this model. class Category: Codable { @Attribute(.unique) var title: String var items: [Item]? @Attribute(.transformable(by: ColorValueTransformer.self)) var color: UIColor? init(title: String = "", color: UIColor) { self.title = title self.color = color } enum CodingKeys: String, CodingKey { case title } required init(from decoder: Decoder) throws { ... } func encode(to encoder: Encoder) throws { ... } } Within my value transformer, I'm handling setting and getting the value. final class ColorValueTransformer: ValueTransformer { static let name = NSValueTransformerName(rawValue: String(describing: ColorValueTransformer.self)) override func transformedValue(_ value: Any?) -> Any? { guard let color = value as? UIColor else { return nil } do { let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: true) return data } catch { return nil } } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let data = value as? Data else { return nil } do { let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) return color } catch { return nil } } public static func register() { let transformer = ColorValueTransformer() ValueTransformer.setValueTransformer(transformer, forName: name) } } Then within my root app entry point, I register this transformer. @main struct ToDosApp: App { ...... init() { ColorValueTransformer.register() } ...... Unfortunately in my custom container object I get a crash on this line let container = try ModelContainer(for: ....) With an error of Thread 1: EXC_BAD_ACCESS (code=1, address=0x0) Like i said before previously this was working fine but now it's not... I have a feedback open also FB13471979 but it would be great if someone on the SwiftData team at Apple could look into this issue since it's a pretty big regression...
Posted
by tundsdev.
Last updated
.
Post not yet marked as solved
2 Replies
144 Views
Many of Apple's tutorials on SwiftData are written alongside SwiftUI, including the sample code. Would it be a bad idea to use SwiftData separately? If I don't use SwiftUI, would it be wiser to choose a different database instead?
Posted Last updated
.
Post marked as solved
2 Replies
188 Views
I'm developing an application using SwiftUI and SwiftData. The app includes a pricing section, and I'd like it to have an initial default value for pricing. Additionally, when updating the app on the App Store, I also want to update the prices. However, I'd like users to have the option to create their own prices if they don't want to use the ones I suggest. I want to save these prices obtained from the user because I'll be using these values for some operations later on. I'm wondering how to achieve this. Should I use SwiftData or UserDefaults for managing the data, or should I save the prices to a JSON file? If I opt for saving to a JSON file, would it be possible to update the JSON file when updating the app? Please feel free to ask any questions. Thank you in advance for your responses.
Posted Last updated
.
Post marked as solved
1 Replies
195 Views
Dear all, I'm quite new to SwiftUI. I have a view where I'd like to open a sheet to edit data in a List. Here below the code of the view: import SwiftUI import SwiftData struct SettingsView: View { @Query(sort: \Stagione.idStagione) private var usoStagione: [Stagione] @Environment(\.modelContext) private var context @State private var stagione = String(Calendar.current.component(.year, from: Date())) @State private var miaSquadra = "" @State private var categoria = "" @State private var selStagione = Set<Stagione>() @State private var modificaStagione = false var body: some View { NavigationStack { GroupBox("Stagione") { Form { TextField("Stagione:", text: $stagione) .frame(width: 150) TextField("Categoria:", text: $categoria) .frame(width: 400) TextField("Mia squadra:", text: $miaSquadra) .frame(width: 400) Button("Salva") { let nuovaStagione = Stagione(idStagione: stagione, categoriaStagione: categoria, miaSquadra: miaSquadra) context.insert(nuovaStagione) miaSquadra = "" categoria = "" stagione = String(Calendar.current.component(.year, from: Date())) } .frame(maxWidth: .infinity, alignment: .trailing) .buttonStyle(.borderedProminent) .disabled(categoria.isEmpty || miaSquadra.isEmpty) } List(selection: $selStagione) { ForEach(usoStagione, id: \.self) { stag in VStack(alignment: .leading) { Text("\(stag.idStagione)").font(.title2) Text("\(stag.miaSquadra)").foregroundStyle(.secondary) Text("\(stag.categoriaStagione)").foregroundStyle(.secondary) } } .contextMenu() { Button(action: { selStagione.forEach(context.delete) }) { Text("Cancella") } Button(action: { self.modificaStagione = true }) { Text("Modifica") } } } .sheet(isPresented: $modificaStagione) { ModificaStagione(stagione: Stagione) } .listStyle(.plain) .textFieldStyle(.roundedBorder) .padding() } } .padding() .textFieldStyle(.roundedBorder) .navigationTitle("Impostazioni") GroupBox("Squadre") { } } } #Preview { SettingsView() .environmentObject(NavigationStateManager()) .modelContainer(for: Stagione.self, inMemory: true) } While here below the code of the view ModificaStagione: import SwiftUI struct ModificaStagione: View { @Environment(\.dismiss) private var dismiss let stagione: Stagione @State private var idStagione = "" @State private var categoria = "" @State private var miaSquadra = "" @State private var vistaPrecedente = true var body: some View { VStack (alignment: .leading) { GroupBox { LabeledContent { TextField("", text: $categoria) .frame(width: 400) } label: { Text("Categoria:") } LabeledContent { TextField("", text: $miaSquadra) .frame(width: 400) } label: { Text("Mia squadra:") } if modifica { Button("Aggiorna dati") { stagione.idStagione = idStagione stagione.categoriaStagione = categoria stagione.miaSquadra = miaSquadra dismiss() } .buttonStyle(.borderedProminent) } } .padding() .textFieldStyle(.roundedBorder) .navigationTitle("Modifica dati stagione") } .onAppear { idStagione = stagione.idStagione categoria = stagione.categoriaStagione miaSquadra = stagione.miaSquadra } } var modifica: Bool { categoria != stagione.categoriaStagione || miaSquadra != stagione.miaSquadra } } In the "Setting" view I receive an error message when I call the view "Modifica Stagione "saying that "Cannot convert value of type 'Stagione.Type' to expected argument type 'Stagione'". What am I doing wrong? Thanks in advance, A.
Posted
by AndreB82.
Last updated
.
Post not yet marked as solved
3 Replies
584 Views
In Xcode 15.0.1, I created a new project to start working with SwiftData. I did this by creating a default App project and checking the Use SwiftData checkbox. The resulting project contains just three files: an app entry point file, a ContentView SwiftUI view file, and an Item model file. The only change I made was to annotate the default Item timestamp property with a .transformable attribute. Here is the resulting model: @Model final class Item { @Attribute(.transformable(by: TestVT.self)) var timestamp: Date // Only updated this line init(timestamp: Date) { self.timestamp = timestamp } } And here is the definition of TestVT. It is a basic ValueTransformer that simply tries to store the Date as a NSNumber: // Added this class TestVT: ValueTransformer { static let name = NSValueTransformerName("TestVT") override class func transformedValueClass() -> AnyClass { NSNumber.self } override class func allowsReverseTransformation() -> Bool { true } override func transformedValue(_ value: Any?) -> Any? { guard let date = value as? Date else { return nil } let ti = date.timeIntervalSince1970 return NSNumber(value: ti) } override func reverseTransformedValue(_ value: Any?) -> Any? { guard let num = value as? NSNumber else { return nil } let ti = num.doubleValue as TimeInterval return Date(timeIntervalSince1970: ti) } } And finally, I made sure to register my ValueTransformer but updating the sharedModelContainer definition in the App: var sharedModelContainer: ModelContainer = { ValueTransformer.setValueTransformer(TestVT(), forName: TestVT.name) // Only added this line let schema = Schema([ Item.self, ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() Prior to Xcode 15.1, this was working fine. However, now when I try to create an item when running the app I get the following error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "timestamp"; desired type = NSNumber; given type = __NSTaggedDate; value = 2023-12-14 01:47:11 +0000.' I'm unsure of why this stopped working. The error seems to be complaining about the input being of type Date when NSNumber was expected, but I thought that's what the ValueTransformer was supposed to be doing. Important note: prior to Xcode 15.1, I did not originally override the transformedValueClass() and everything was working but in the new Xcode when launching the app I was getting a Thread 1: EXC_BAD_ACCESS (code=1, address=0x0) on the return try ModelContainer(...) line. Removing the .transformable property from my model fixed the issue. That's why I added the override here, because I think the docs indicate overriding it as well and I missed that the first time. This being said, I think the code I have is what a correct ValueTransformer would look like. If anyone has experienced this issue, or has a working ValueTransformer for SwiftData in Xcode 15.1, please let me know. Appreciate any help with this issue. Thanks so much!
Posted
by Sammcb.
Last updated
.
Post not yet marked as solved
0 Replies
135 Views
iOS17 using swiftData & CloudKit. compiles correctly, then throws error "App installation failed: Unable To Install " Please try again later. Failed to load Info.plist from bundle at path ... /.../simulatorlocation/.app/Frameworks/SwiftData.framework/Info.plist: No such file or directory. same error occurs on physical device 15Pro running iOS17.5 how do I fix this? here is the error: Please try again later. Failed to load Info.plist from bundle at path /Users//Library/Developer/CoreSimulator/Devices/996C811C-EE2D-47AE-881A-D1D2FA830BCC/data/Library/Caches/com.apple.mobile.installd.staging/temp.kkR2I8/extracted/Payload/.app/Frameworks/SwiftData.framework; Extra info about "/Users//Library/Developer/CoreSimulator/Devices/996C811C-EE2D-47AE-881A-D1D2FA830BCC/data/Library/Caches/com.apple.mobile.installd.staging/temp.kkR2I8/extracted/Payload/.app/Frameworks/SwiftData.framework/Info.plist": Couldn't stat /Users//Library/Developer/CoreSimulator/Devices/996C811C-EE2D-47AE-881A-D1D2FA830BCC/data/Library/Caches/com.apple.mobile.installd.staging/temp.kkR2I8/extracted/Payload/.app/Frameworks/SwiftData.framework/Info.plist: No such file or directory
Posted Last updated
.
Post not yet marked as solved
0 Replies
151 Views
Hello. See the code below. struct ContentView: View { var body: some View { TabView { VehicleTab() .tabItem({ Label("Vehicles", systemImage: "car.fill")}) .modelContainer(for: Vehicle.self) TransactionsTab() .tabItem { Label("Transactions", systemImage: "dollarsign") } .modelContainer(for: Transaction.self) } } } Using the .modelContainer() in this way seems to be causing some issue. I was under the assumption that this would just create a container for each view. I get the error below in this configuration. If I comment out either one of the .modelContainer() modifiers, it works fine. Query encountered an error: Error Domain=NSCocoaErrorDomain Code=256 "The file “default.store” couldn’t be opened." Are you not able to do what I'm doing? Is there a way to have two separate containers?
Posted
by Xavier-k.
Last updated
.
Post not yet marked as solved
1 Replies
197 Views
Hi, just trying to learn how to work with mainActor. I am in a need of analyzing users data with API service one a background. Whenever user saves a post into SwiftData, I need to analyze that posts asynchronously. Here is my current code, which by the way works, but I am getting warning here; actor DatabaseInteractor { let networkInteractor: any NetworkInteractor = NetworkInteractorImpl() func loadUserProfile() async -&gt; String { do { let objects = try await modelContainer.mainContext.fetch(FetchDescriptor&lt;ProfileSwiftData&gt;()) if let profileTest = objects.first?.profile { return profileTest } } catch { } return "" } I get a warning on let objects line. Warning: Non-sendable type 'ModelContext' in implicitly asynchronous access to main actor-isolated property 'mainContext' cannot cross actor boundary
Posted
by Minatory.
Last updated
.
Post marked as solved
1 Replies
142 Views
I used Query(filter:sortBy:) and show them in the ListView. When I change the property of the model, because I fetched filtered and sorted array, the model immediately reflect the change and find its position. What I want is at initializing it fetches filtered and sorted array. And when I change the property nothing happens. But when I tap on refresh button, then the array should change. For example in ToDoList app, the array consists of not done works. when I tap done, it should stay until I refresh. Is it possible?
Posted
by Rayy123.
Last updated
.
Post not yet marked as solved
1 Replies
170 Views
Hey Everyone, I am working on this project and it has to be done really soon. I have 30 TextFields and I need it to be saved to the whole app so If I had 1 phone inputting it, it would sync to another one with the same text value in it. So say I put "Done" in one TextField on Phone 1 then it'll save and update on Phone 2. Please get back to me when someone can. I have been developing in Xcode for about 7 months now and I am just running into it right now! Thanks so much
Posted Last updated
.
Post not yet marked as solved
0 Replies
169 Views
iPad mini device with iPadOS 17.4.1. I get this failure on iPad mini device exclusively. I have universal app and I doesn't get this error neither on iPhone or any simulator or macOS. I tried to reset the iPad, still getting the error. The error happens when I send the app to background from the particular app screen. Here is the error: error: Store failed to load. <NSPersistentStoreDescription: 0x3004b4bd0> (type: SQLite, url: file:///dev/null) with error = Error Domain=NSCocoaErrorDomain Code=134060 "A Core Data error occurred." UserInfo={NSLocalizedFailureReason=The configuration named 'default' does not contain any entities.} with userInfo { NSLocalizedFailureReason = "The configuration named 'default' does not contain any entities."; } What error says is that it loaded the store from file:///dev/null, which is wrong. Basically it looses the model container which is added via modelContaner modificator to the root view. The error get caused particularly by the @Environment(\.modelContext) private var modelContext call in the view on which the failure occurs. This view is deep in the view hierarchy, and it get's created in the navigationDestination block. I fixed the error by supplying modelContainer one more time right on the view: .navigationDestination(for: File.self) { file in FileEditor(file: file) .modelContainer(FolderService.modelContainer) } I wonder, why can it loose the model container which is supplied on the root view?
Posted
by yukas.
Last updated
.
Post not yet marked as solved
4 Replies
1.1k Views
In Core Data, I can merge two predicates (perform an OR operation) using the following code: let compound3 = NSCompoundPredicate(type: .or, subpredicates: [compound1, compound2]) How can I achieve a similar operation in SwiftData with new Predicate?
Posted
by Fat Xu.
Last updated
.
Post marked as solved
6 Replies
459 Views
My App keeps crashing in the background and I don't know why. I'm using SwiftData and SwiftUI. I'm setting up a .backgroundTask like this: import SwiftUI import SwiftData import TipKit @main struct MyAppName: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @State var navManager = NavigationManager.load() @State var alerter: Alerter = Alerter() var sharedModelContainer: ModelContainer = { do { return try ModelContainer(for: DataController.schema, configurations: [DataController.modelConfig]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() var body: some Scene { WindowGroup { SetupView() .accentColor(.myAccentColor) .environment(alerter) .alert(isPresented: $alerter.isShowingAlert) { alerter.alert ?? Alert(title: Text(verbatim: "")) } .task { try? Tips.configure([ .displayFrequency(.immediate), .datastoreLocation(.applicationDefault) ]) } .onAppear { setUpAppDelegate() } } .modelContainer(sharedModelContainer) .backgroundTask(.appRefresh(Const.backgroundAppRefreshId)) { @MainActor in let container = sharedModelContainer await RefreshManager.handleBackgroundVideoRefresh(container) } .environment(navManager) } func setUpAppDelegate() { appDelegate.navManager = navManager } } The RefreshManager.handleBackgroundRefresh(...) goes on to load data and then insert models for them via SwiftData. It only happens occasionally and afaik only in the background. Weirdly enough the issue seems to be there even when I only print something in the background task. Even when I don't schedule/set a background task at all. How can that be? The crashes started in the version that included the .backgroundTask, although perhaps it's related to something else. I'm still trying to further narrow it down. This is the crash report that I'm getting: Exception Type: EXC_BREAKPOINT (SIGTRAP) Exception Codes: 0x0000000000000001, 0x00000001a1bb98c0 Termination Reason: SIGNAL 5 Trace/BPT trap: 5 Terminating Process: exc handler [809] Triggered by Thread: 0 Thread 0 name: Thread 0 Crashed: 0 libswiftCore.dylib 0x00000001a1bb98c0 _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144) 1 libswiftCore.dylib 0x00000001a1c27d14 swift_unexpectedError + 664 (ErrorType.swift:188) 2 _SwiftData_SwiftUI 0x000000024310cd78 one-time initialization function for empty + 300 (ModelContainer+Extensions.swift:5) 3 libdispatch.dylib 0x00000001ab16add4 _dispatch_client_callout + 20 (object.m:576) 4 libdispatch.dylib 0x00000001ab16c654 _dispatch_once_callout + 32 (once.c:52) 5 _SwiftData_SwiftUI 0x000000024310cdf8 one-time initialization function for empty + 124 (ModelContainer+Extensions.swift:12) 6 libdispatch.dylib 0x00000001ab16add4 _dispatch_client_callout + 20 (object.m:576) 7 libdispatch.dylib 0x00000001ab16c654 _dispatch_once_callout + 32 (once.c:52) 8 _SwiftData_SwiftUI 0x0000000243122170 key path getter for EnvironmentValues.modelContext : EnvironmentValues + 140 (<compiler-generated>:0) 9 libswiftCore.dylib 0x00000001a1ce4628 RawKeyPathComponent._projectReadOnly<A, B, C>(_:to:endingWith:) + 1012 (KeyPath.swift:1701) 10 libswiftCore.dylib 0x00000001a1ce3ddc KeyPath._projectReadOnly(from:) + 1036 (KeyPath.swift:331) 11 libswiftCore.dylib 0x00000001a1ce8348 swift_getAtKeyPath + 24 (KeyPath.swift:2029) 12 SwiftUI 0x00000001a7af4814 EnvironmentBox.update(property:phase:) + 872 (Environment.swift:273) 13 SwiftUI 0x00000001a782a074 static BoxVTable.update(ptr:property:phase:) + 396 (DynamicPropertyBuffer.swift:294) 14 SwiftUI 0x00000001a78297b0 _DynamicPropertyBuffer.update(container:phase:) + 104 (DynamicPropertyBuffer.swift:215) 15 SwiftUI 0x00000001a887fb78 closure #1 in closure #1 in DynamicBody.updateValue() + 104 (DynamicProperty.swift:447) 16 SwiftUI 0x00000001a887fbb8 partial apply for closure #1 in closure #1 in DynamicBody.updateValue() + 28 (<compiler-generated>:0) 17 libswiftCore.dylib 0x00000001a1bcc068 withUnsafeMutablePointer<A, B>(to:_:) + 28 (LifetimeManager.swift:82) 18 SwiftUI 0x00000001a887f9dc closure #1 in DynamicBody.updateValue() + 408 (DynamicProperty.swift:446) 19 SwiftUI 0x00000001a887f5c0 DynamicBody.updateValue() + 712 (DynamicProperty.swift:445) 20 SwiftUI 0x00000001a71e8bf8 partial apply for implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) + 32 (<compiler-generated>:0) 21 AttributeGraph 0x00000001cbd4c240 AG::Graph::UpdateStack::update() + 512 (ag-graph-update.cc:578) 22 AttributeGraph 0x00000001cbd42f38 AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 424 (ag-graph-update.cc:719) 23 AttributeGraph 0x00000001cbd42810 AG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, unsigned int, AGSwiftMetadata const*, unsigned char&, long) + 720 (ag-graph.cc:1429) 24 AttributeGraph 0x00000001cbd423a4 AGGraphGetValue + 228 (AGGraph.mm:701) 25 SwiftUI 0x00000001a887f548 DynamicBody.updateValue() + 592 (DynamicProperty.swift:444) 26 SwiftUI 0x00000001a71e8bf8 partial apply for implicit closure #1 in closure #1 in closure #1 in Attribute.init<A>(_:) + 32 (<compiler-generated>:0) 27 AttributeGraph 0x00000001cbd4c240 AG::Graph::UpdateStack::update() + 512 (ag-graph-update.cc:578) 28 AttributeGraph 0x00000001cbd42f38 AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) + 424 (ag-graph-update.cc:719) [...] 107 SwiftUI 0x00000001a7cf79fc static App.main() + 132 (App.swift:114) 108 MyAppName 0x0000000100e6d120 static MyAppNameApp.$main() + 52 (MyAppNameApp.swift:0) 109 MyAppName 0x0000000100e6d120 main + 64 110 dyld 0x00000001c67c2d84 start + 2240 (dyldMain.cpp:1298) The report also says key path getter for EnvironmentValues.modelContext, which seems odd. Any idea where I could start to look for the issue? I'm currently just trying things out, pushing them to TestFlight and waiting for crashes to happen. As soon as I can narrow it down further I'll update this.
Posted
by fer0n.
Last updated
.
Post not yet marked as solved
2 Replies
184 Views
I'm using SwiftData with SwiftUI. I want to disable a contextMenu button on a list item conditionally depending on the list item itself. E.g. I have a parent-child model as one-to-many relationship. On ParentListView, I need to disable the contextMenu button if parent.children is not empty. Once user deletes the children in another ChildrenListView, switching back to the ParentView should now enable the delete button. struct ParentListView: View { @Environment(\.modelContext) private var modelContext private var parents: [Parent] // Intention: only allow delete if parent.children is empty // And parent.children can be updated in another ChildrenView @State private var disableDelete = true var body: some View { List(parents) { parent in MyListItemView(parent) .contextMenu { Button { if parent.children.isEmpty { modelContext.delete(parent) } } label: { Text("Delete") } .disabled(disableDelete) // TODO: this doesn't work. Question: (a) why? (b) how to fix? .onAppear { if parent.children.isEmpty { disableDelete = false } else { disableDelete = true } } } } } } Question: The onAppear seems to be triggered upon rendering the whole ParentListView, not the contextMenu button. Why? (and what's the general rule when this can happen?) how to fix it? I think I'm flexible in terms of UI presentation. E.g. it doesn't have to be a contextMenu, it can be a button next to the list item, a command menu or something else suit for the job. For context, I'm trying to find a workaround for SwiftData crash on deletion with relation by manually implementing the deleteRule: .deny. Appendix: MRE ExampleModel.swift import SwiftData @Model final class Parent { @Attribute(.unique) var name: String @Relationship(deleteRule: .deny, inverse: \Child.parent) var children: [Child] = [] init(name: String) { self.name = name } } @Model final class Child { @Attribute(.unique) var name: String var parent: Parent init(name: String, parent: Parent) { self.name = name self.parent = parent } } ExampleView.swift import SwiftData import SwiftUI struct ExampleView: View { @Environment(\.modelContext) private var modelContext @Query private var parents: [Parent] @Query private var children: [Child] @State private var disableDelete = true var body: some View { VStack { List(parents) { parent in Text(parent.name) .contextMenu { Button { modelContext.delete(parent) } label: { Text("Delete") } // TODO: this doesn't work. Question: (a) why? (b) how to fix? .disabled(disableDelete) .onAppear { if !parent.children.isEmpty { disableDelete = true } else { disableDelete = false } } } } Spacer() Button { addNewParent() } label: { Text("Add parent") .frame(maxWidth: .infinity) .bold() } .background() Divider() List(children) { child in Text("\(child.name) from: \(child.parent.name)") .contextMenu { Button { modelContext.delete(child) } label: { Text("Delete") } } } Spacer() Button { addNewChild() } label: { Text("Add child") .frame(maxWidth: .infinity) .bold() } .background() } } func addNewParent() { let newParent = Parent( name: "New Parent " + Int.random(in: 1...100).description ) modelContext.insert(newParent) } func addNewChild() { let newChild = Child( name: "New Child " + Int.random(in: 1...100).description , parent: parents.randomElement()! ) modelContext.insert(newChild) } } #Preview { ExampleView() .modelContainer( for: [ Parent.self, Child.self ], inMemory: true ) } How to reproduce: AddParent Right click parent, expect delete button enabled AddChild Right click parent, expect delete button disabled Screenshot: https://i.stack.imgur.com/AAvD6.png
Posted
by mark374.
Last updated
.