Tyler Durden
Tyler Durden

Reputation: 645

How to make SwiftUI's NavigationView remember the state of its members in macOS?

I have implemented a SwiftUI NavigationView in macOS as follows:

struct App1: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                SideBar()
                Text("SCREEN 1")
            }
        }
    }
}

where SideBar() is a list of navigation links as follows:

struct SideBar: View {
    var body: some View {
        List {
            NavigationLink(destination: SomeListView1()){
                Text("Item1")
            }
            NavigationLink(destination: SomeListView2()){
                Text("Item2")
            }
            NavigationLink(destination: SomeListView3()){
                Text("Item3")
            }
        }
    }
}

where SomeListViewn() contains a list of items.

When I click different navigation links in the side bar, the related views are reloaded each time. For example, if I was to click Item1 and scroll to the bottom of the list of items in SomeListView1(), then switch to Item2 and back to Item1, SomeListView1() is reloaded, i.e., it forgets its scroll position and re-initialises the view. I want the navigation views to remember their state so when you switch to different items in the sidebar and switch back, you can continue where you left off.

How can this be achieved?

Upvotes: 3

Views: 796

Answers (1)

Leszek Szary
Leszek Szary

Reputation: 10346

Instead of using NavigationView what you might want is to use a TabView with new .sidebarAdaptable style like in this example:

struct TestView: View {
    @State private var counter = 0
    var title: String
    var body: some View {
        VStack {
            Text("Title: \(title)")
            Text("Counter: \(counter)")
            Button("+1") {
                counter += 1
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        TabView {
            TestView(title: "First tab")
                .tabItem {
                    Label("First", systemImage: "f.circle")
                }
            TestView(title: "Second tab")
                .tabItem {
                    Label("Second", systemImage: "s.circle")
                }
            TestView(title: "Third tab")
                .tabItem {
                    Label("Third", systemImage: "t.circle")
                }
        }
        .tabViewStyle(.sidebarAdaptable)
    }
}

sidebarAdaptable

With this style TabView on macOS will have a sidebar similarly as if you used a navigation view but the views inside it will keep state when you change the view which you can check with above example by tapping on the button and changing the view. Note however that this is only available on macOS 15+ and unfortunately afaik it only recently started to work correctly (on macOS 15.2 and lower it had a bug which caused it to not capture clicks correctly when clicked on the text, see: https://developer.apple.com/forums/thread/764764).

If you need to support lower version on macOS and therefore need to use NavigationView / NavigationSplitView the simplest alternative solution would probably be to use @SceneStorage to keep the state of each view (assuming you use unique views inside the sidebar that do not repeat).

Upvotes: 0

Related Questions