Home > Mobile >  SwiftUI Combine: TabView is not updating on selection when property stored in different viewmodel
SwiftUI Combine: TabView is not updating on selection when property stored in different viewmodel

Time:05-20

I'm working on Tabview with page style and I want to scroll tabview on button actions. Buttons are added inside NavigationMenu.

NavigationMenu view and NavigationModel(ViewModel) are separated from a parent.

Selection handling is done inside NavigationModel.

On tab page swipe I'm able to see the change in NavigationMenu which is fine.

But if I tap on buttons the tabview page is not swiping. Even I receive change event on method onReceive.

Code:

import SwiftUI
import Combine

final class NavigationModel: ObservableObject {
    @Published var selectedItem = ""
    @Published var items: [String] = [
        "Button 1", "Button 2", "Button 3"
    ]
}

struct NavigationMenu: View {

    @ObservedObject var viewModel: NavigationModel

    var body: some View {
        HStack {
            ForEach(0..<3, id: \.self) { index in
                let title = viewModel.items[index]
                Button {
                    viewModel.selectedItem = title
                } label: {
                    Text(title)
                        .font(.system(.body))
                        .padding()
                        .foregroundColor(
                            viewModel.selectedItem == title ? .white : .black
                        )
                        .background(viewModel.selectedItem == title ? .black : .yellow)
                }
            }
        }
    }
}

final class TabViewModel: ObservableObject {
    var navModel = NavigationModel()
}

struct TabviewWithMenuView: View {

    @ObservedObject var viewModel = TabViewModel()

    var body: some View {
        parentView
    }

    private var parentView: some View {
        VStack(spacing: 0) {
            Spacer()
            NavigationMenu(viewModel: viewModel.navModel)
            pageView
        }
        .onReceive(viewModel.navModel.$selectedItem) { output in
            print("Button tapped:", output)
        }
    }

    private var pageView: some View {
        TabView(selection: $viewModel.navModel.selectedItem) {
            ForEach(0..<3, id: \.self) { index in
                let tag = viewModel.navModel.items[index]
                item(tag: tag)
                    .tag(tag)
            }
        }
        .tabViewStyle(.page(indexDisplayMode: .never))
        .transition(.slide)
    }

    private func item(tag: String) -> some View {
        VStack {
            Text("PAGE: "   tag)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.red)
    }

}

Image:

CodePudding user response:

ObservableObject inside ObservableObject is not observed, we need to observe explicitly the instance which is changed.

A possible solution in this case is to separate PageView and inject navigation view model to it so it would be observed.

Tested with Xcode 13.3 / iOS 15.4

demo

Here is main part:


    NavigationMenu(viewModel: viewModel.navModel)
    PageView(navModel: viewModel.navModel)

    ...

    struct PageView: View {
        @ObservedObject var navModel: NavigationModel

        var body: some View {
            pageView
        }
        // ....
    }

Test module in project is here

  • Related