I'm using a two-column NavigationSplitView to display SwiftData objects in a macOS app. After you add a new object, I want it to be selected in the sidebar. Although I've found a way to do this, it feels awkward and I'm wondering if there's a simpler solution I'm missing.
The issue is that you can't simply set the selection directly after adding an object, because you're only inserting it into the SwiftData model context, not directly into the array, which is a @Query
that needs to update separately. So the array won't yet contain the new object at the point where you would try to set the selection.
To get around this, my code remembers the new object in an addedItem
variable, waits for the @Query
array to get updated using onChange(of:)
and sets the selection from there.
Here's essentially what I have:
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
@State private var selectedItem: Item?
@State private var addedItem: Item? // PART OF MY SOLUTION
var body: some View {
NavigationSplitView {
List(selection: $selectedItem) {
ForEach(items) { item in
NavigationLink(value: item,
label: { Text(verbatim: item.name) })
}
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
.toolbar {
Button("Add", systemImage: "plus", action: addItem)
}
} detail: {
DetailView(item: selectedItem)
}
.task {
selectedItem = items.first
}
// PART OF MY SOLUTION:
.onChange(of: items) { oldValue, newValue in
if let addedItem, items.contains(addedItem) {
selectedItem = addedItem
}
addedItem = nil
}
}
private func addItem() {
let newItem = Item()
withAnimation {
modelContext.insert(newItem)
//selectedItem = newItem // THIS WOULDN'T WORK
}
addedItem = newItem // PART OF MY SOLUTION
}
}
Is there a more elegant solution?