SwiftUI TabView selection in @Observable resets content on tab change

143 Views Asked by At

I'm creating an app in SwiftUI, where I want to have @Observed class keeping selected tab and maybe navigation path in the future. Architecture I'm working on is MVVM. Currently when I switch tab and Navigator class is changed, all Views in TabView are re-created. They are not preserving the state. When I'm moving selectedTab value to @State it works fine.

import SwiftUI

@Observable
class Navigator {
    var selectedTab = Tab.one
}

enum Tab: Equatable {
    case one, two
}

struct ContentView: View {
    @State private var navigator = Navigator()

    var body: some View {
        TabView(selection: $navigator.selectedTab) {
            PageOne(viewModel: ViewModel(count: 0))
                    .tabItem { Text("one") }
                    .tag(Tab.one)

                PageTwo(viewModel: ViewModel(count: 0))
                    .tabItem { Text("two") }
                    .tag(Tab.two)
            }
            .padding()
    }

}

struct PageOne: View {
    var viewModel: ViewModel

    var body: some View {
        VStack {

            Button("increase page One") {
                viewModel.count += 1
            }

            Text("Current count \(viewModel.count)")
        }
    }
}

@Observable
class ViewModel {
    var count: Int

    init(count: Int) {
        self.count = count
    }
}

struct PageTwo: View {
    var viewModel: ViewModel

    var body: some View {
        VStack {
            Button("increase page two") {
                viewModel.count += 1
            }

            Text("Current count \(viewModel.count)")
        }
    }
}
2

There are 2 best solutions below

0
On

You should hold on to the models, otherwise they will be lost on re-rendering the body:

struct ContentView: View {
    @State private var navigator = Navigator()
    let pageOneModel = ViewModel(count: 0) // 
    let pageTowModel = ViewModel(count: 0) // 

    var body: some View {
        TabView(selection: $navigator.selectedTab) {
            PageOne(viewModel: pageOneModel) // 
                .tabItem { Text("one") }
                .tag(Tab.one)

            PageTwo(viewModel: pageTowModel) // 
                .tabItem { Text("two") }
                .tag(Tab.two)
        }
        .padding()
    }
}
1
On

Adding @State in front of view model did resolve the issue. Changed part looks like this now:

@State var viewModel: ViewModel