Right way to select newly added SwiftData object in NavigationSplitView?

111 Views Asked by At

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?

0

There are 0 best solutions below