Navigation split view selection property is set to nil when selecting list item

I am making a SwiftUI Mac app that uses navigation split view and the Observation framework. The split view has a sidebar list, a detail view, and a selection property to store the selected item in the list. I set the initial selection to the first item in the list using the .onAppear modifier.

When I select an item from the list, I get the desired behavior. The detail view shows the contents of the selected item. But the selection property’s value changes to nil. Because selection is nil, I am unable to remove items from the list.

Model Code

struct Wiki {
    var pages: [Page] = []
}

@Observable
class Page: Identifiable, Equatable, Hashable {
    let id = UUID()
    var title: String = "Page"
    var text: String
    
    static func == (lhs: Page, rhs: Page) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(title)
        hasher.combine(text)
    }
    
}

Split View Code

struct ContentView: View {
    @Binding var wiki: Wiki
    @State private var selection: Page? = nil
    
    var body: some View {
        NavigationSplitView {
            PageList(wiki: $wiki, selection: $selection)
                .navigationSplitViewColumnWidth(ideal: 192)
        } detail: {
            if let selection {
                PageView(page: selection)
            } else {
                Text("Select a page to view its contents.")
            }
        }
        .onAppear {
            selection = wiki.pages.first
        }
    }
}

List Code

struct PageList: View {
    @Binding var wiki: Wiki
    @Binding var selection: Page?
    
    var body: some View {
        VStack {
            Text("Pages")
                .font(.title)
            List($wiki.pages, selection: $selection) { $page in
                NavigationLink {
                    PageView(page: page)
                } label: {
                    TextField("", text: $page.title)
                }
            }
            .padding()
        }
    }
}

Detail View Code

struct PageView: View {
    @Bindable var page: Page
    
    var body: some View {
        TextEditor(text: $page.text)
    }
}

I tried changing selection in the list view from @Binding to @Bindable, but I get the following build error:

'init(wrappedValue:)' is unavailable: The wrapped value must be an object that conforms to Observable

What fix do I have to make to get the selection property to not be nil when I select an item from the list?

I have taken the liberty of tweaking your code a bit which also fixes your issue of not being able to delete a page from sidebar list which shows all your pages in the wiki.

Wiki and PageView stay the same.

PageList

struct PageList: View {
    @Binding var wiki: Wiki
    @Binding var selection: Page?
    
    var body: some View {
        List(selection: $selection) {
            Section {
                ForEach($wiki.pages, id: \.id) { $page in
                    NavigationLink(value: page) {
                        TextField("", text: $page.title)
                    }
                    .contextMenu {
                        Button("Delete Page") {
                            wiki.pages.removeAll(where: { $0.id == page.id })
                        }
                    }
                }
            } header: {
                Text("Pages")
            }
        }
        .toolbar {
            ToolbarItemGroup (placement: .navigation) {
                Spacer()
                
                Button {
                    wiki.pages.append(.init(title: "New Page", text: ""))
                } label: {
                    Label("Add", systemImage: "plus")
                }
            }
        }
    }
}

List stores the selection. ForEach is added with stores the $wiki.pages array. NavigationLink is updated to use the new .navigationDestination() modifier in ContentView. Added a contextMenu() to the NavigationLink. Added an Add Button in the toolbar.

ContentView

struct ContentView: View {
    @Binding var wiki: Wiki
    @State private var selection: Page? = nil
    
    var body: some View {
        NavigationSplitView {
            PageList(wiki: $wiki, selection: $selection)
                .navigationSplitViewColumnWidth(ideal: 192)
                .navigationDestination(item: $selection) { selection in
                    PageView(page: selection)
                }
        } detail: {
            Text("Select a page to view its contents.")
        }
        .onAppear {
            selection = wiki.pages.first
        }
    }
}

Detail View only shows the Text because the .navigationDestination() now handles the PageView. If you have persistence, then the onAppear will work but otherwise it doesn't do anything as of right now since the array will be wiped each time you relaunch the app.

I would also suggest changing @Binding var wiki: wiki to an @State variable if you do not want your entire app across all open windows to share the same state. For example: I open a new window to edit a different Wiki. However since the state is at the App level, every WindowGroup will show the exact same Wiki.

LMK if this resolves your issue.

Your changes solved my issue. Thank you.

I didn't mention it in my question, but the app is document-based so each window will have its own wiki.

Navigation split view selection property is set to nil when selecting list item
 
 
Q