Weird behavior with NavigationSplitView and @State

I have a NavigationSplitView in my app, I have an @State variable in my detail view that gets created in init.

When I select something from the sidebar and the detail view renders, at first everything looks ok. But when I select a different item on the sidebar, the contents of the @state variable don't get recreated.

Using the debugger I can see the init of the detail view get called every time I select a new item in the sidebar, and I can see the @State variable get created. But when it actually renders, the @State variable still contains the previous selection's values.

I've reduced this problem to a test case I'll paste below. The top text in the detail view is a variable passed in from the sidebar, and the second line of text is generated by the @State variable. Expected behavior would be, if I select "one" the detail view would display "one" and "The name is one". If I select "two" the detail view would display "two" and "The name is two".

Instead, if I select "one" first, it displays correctly. But when I select "two", it displays "two" and "The name is one".

Note that if I select "two" as the first thing I do after launching the app, it correctly displays "two" and "The name is two", but when I click on "one" next, it will display "one" and "the name is two". So the state variable is being set once, then never changing again,

Here's the sample code and screenshots:

import SwiftUI

struct Item: Hashable, Identifiable {
    var id: Self {self}
    let name: String
}

struct ContentView: View {
    
    var items: [Item]
    @State private var selectedItem: Item? = nil
    
    init() {
        self.items = [Item(name: "one"), Item(name: "two"), Item(name: "three")]
    }
    
    var body: some View {
        NavigationSplitView{
            List(selection: $selectedItem) {
                ForEach(items) { item in
                    Text(item.name)
                }
            }
        } detail: {
            if let name = selectedItem?.name {
                DetailView(name: name)
            } else {
                Text("Select an item")
            }
        }
    }
}

struct DetailView: View {
    
    @State var detailItem: DetailItem
    
    var name: String
    
    init(name: String) {
        self.name = name
        _detailItem = State(wrappedValue: DetailItem(name: name))
    }
    
    var body: some View {
        VStack {
            Text(name)
            Text(detailItem.computedText)
        }
    }
}

struct DetailItem {
    let name: String
    
    var computedText: String {
        return "The name is \(name)"
    }
}

@natemartinsf Pass a binding, don't initialise the @State variable from outside

Exact same issue her. Any workaround ? I need to have @State in detailView for TextField.

Same issue is happening when you have Three Column layout. But it seems to be more visible what is happening.

When I select other category in the main sidebar, the selected item in secondary sidebar won't get unselected, plus new item from selected category gets selected.

So now after two clicks I have somehow two selected items in my secondary sidebar and when i'll continue playing with it I'll get thread ERROR.

The problem is the automatic selection of item in second list. you can't touch on that and reset it when the category from primary sidebar is changed.

What is really surprising for me is that I haven't found any threads about this so far, even though this behavior is happening in the most basic example of three column layout where you have same items in different categories.

@natemartinsf You may have to make DetailItem Observable, otherwise changes do not cause View update

Or move var computedText: String into DetailView, but that does not explain your issue.

Could do with Binding:

struct Item: Hashable, Identifiable {
    var id: Self {self}
    let name: String
}

struct ContentView: View {
    
    var items: [Item]
    @State private var selectedItem: Item? = nil
    @State private var detailItem: DetailItem? = nil

    init() {
        self.items = [Item(name: "one"), Item(name: "two"), Item(name: "three")]
    }
    
    var body: some View {
        NavigationSplitView {
            List {  // onTap for selection
                ForEach(items) { item in
                    Text(item.name)
                        .onTapGesture {
                            selectedItem = item
                            detailItem = DetailItem(name: item.name)
                        }
                }
            }
        } detail: {
            if let _ = selectedItem {
                DetailView(detail: $detailItem)
            } else {
                Text("Select an item")
            }
        }
    }
}

struct DetailView: View {
    
    @Binding var detail: DetailItem?
    
    var body: some View {
        VStack {
            Text(detail!.name)
            Text(detail!.computedText)
        }
    }
}

struct DetailItem {
    let name: String
    
    var computedText: String {
        return "The name is \(name)"
    }
}
Weird behavior with NavigationSplitView and @State
 
 
Q