Posts

Post not yet marked as solved
2 Replies
207 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
.