I have created a minimum example to demonstrate an issue with observing SwiftData model and UndoManager. This project includes a simple NavigationSplitView, an Item SwiftData model that is being persisted and an enabled UndoManager.
Problem: The SwiftData model Item
can be observed as expected. Changing the date in the DetailView
works as expected and all related views (ListElementView
+ DetailView
) are updated as expected. When pressing ⌘+Z to undo with the enabled UndoManager, deletions or inserts in the sidebar are visible immediately (and properly observed by ContentView
). However, when changing the timestamp and pressing ⌘+Z to undo that change, it is not properly observed and immediately updated in the related views (ListElementView
+ DetailView
).
Further comments:
- Undo operation to the model value changes (here:
timestamp
) are visible in theDetailView
when changing sidebar selections - Undo operation to the model value changes (here:
timestamp
) are visible in theListElementView
when restarting the app - Undo operation to the model value changes (here:
timestamp
) are are properly observed and immediately visible in the sidebar, when ommiting theListElementView
(no view encapsulation)
Relevant code base:
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
@State private var selectedItems: Set<Item> = []
var body: some View {
NavigationSplitView {
List(selection: $selectedItems) {
ForEach(items) { item in
ListElementView(item: item)
.tag(item)
}
.onDelete(perform: deleteItems)
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
.toolbar {
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
} detail: {
if let item = selectedItems.first {
DetailView(item: item)
} else {
Text("Select an item")
}
}
.onDeleteCommand {
deleteSelectedItems()
}
}
private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
private func deleteSelectedItems() {
for selectedItem in selectedItems {
modelContext.delete(selectedItem)
selectedItems.remove(selectedItem)
}
}
}
struct ListElementView: View {
@Bindable var item: Item
var body: some View {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
}
}
struct DetailView: View {
@Bindable var item: Item
var body: some View {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
DatePicker(selection: $item.timestamp, label: { Text("Change Date:") })
}
}
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
It seems that the UndoManager does not trigger a redraw of the ContentView
through the items
query? Is this a bug or a feature?