0

I have a view model that is parent to other children view models. That is:

public class ViewModel: ObservableObject {
    
    @Published var nav = NavigationViewModel()
    @Published var screen = ScreenViewModel()

The other children view model, such as nav and screen, all serve a specific purpose. For example, nav’s responsibility is to keep track of the current screen:

class NavigationViewModel: ObservableObject {
    
    // MARK: Publishers
    @Published var currentScreen: Screen = .Timeline
    
}

The ViewModel is instantiated in the App struct:

    @main
struct Appy_WeatherApp: App {
    
    // MARK: Global
    var viewModel = ViewModel()
    
    // MARK: -
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(viewModel)
        }
    }
}

And I declare an @EnvironmentObject for it on any view that needs access to it:

@EnvironmentObject var viewModel: ViewModel 

Any view referencing a non-object property of ViewModel that is being @Published whose value changes will result in the view to be re-rendered as expected. However, if the currentScreen @Published property of the NavigationViewModel changes, for example, then the view is not being re-rendered.

I know I can make it work if I separate NavigationViewModel from ViewModel, instantiate it at the app level and use it as its own environment object in any views that access any of its published properties.

My question is whether the above workaround is actually the correct way to handle this, and/or is there any way for views to be subscribed to value changes of properties inside child objects of environment objects? Or is there another way that I’ve not considered that’s the recommended approach for what I’m trying to achieve through fragmentation of view model responsibilities?

1 Answer 1

1

There are several ways to achieve this.

Option 1

Using Combine.

import Combine

public class ViewModel: ObservableObject {

    @Published var nav = NavigationViewModel()
    var anyCancellable: AnyCancellable?

    init() {
        anyCancellable = nav.objectWillChange.sink { _ in
            self.objectWillChange.send()
        }
    }
}

You basically just listen to whenever your navigationViewModel publishes changes. If so, you tell your views that your ViewModel has changes aswell.

Option 2

I suppose due to the name NavigationViewModel, that you would use it quite often inside other view models?

If that's the case, I would go for a singleton pattern, like so:

class NavigationViewModel: ObservableObject {

    static let shared = NavigationViewModel()
    private init() {}

    @Published var currentScreen: Screen = .Timeline     
}

Inside your ViewModel:

public class ViewModel: ObservableObject {

    var nav: NavigationViewModel { NavigationViewModel.shared }
}

You can of course also call it inside any View:

struct ContentView: View {

    @StateObject var navigationModel = NavigationModel.shared
}

You might have to call objectWillChange.send() after changing publishers.

@Published var currentScreen: Screen = .Timeline {
    didSet { 
        objectWillChange.send()
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Option 1 was exactly what I was after, thank you! I am curious about elements of Option 2 though. Yep, I expect view models need access to others at different points. Same goes to views. Of course, I can access them through the view model, but to keep things more concise, would I be able to use StateObject a .shared instance across views? That said, isn’t a StateObject meant to be used if the object’s source of truth/origin is the view itself?
Perfect, happy to hear. Hmm, good point. I can tell you that I have done that for my current app, (around 50 times) and I haven't found anything fraud yet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.