Search code examples
swiftswiftuisingletonobservableobject

SwiftUI Classes that conforms ObservableObject should be Singleton?


I'm considered newby in SwiftUI and I have the below ViewModel. But I'm not sure MyViewModel should be singleton. Is that usage is right? And what is the best practice/usage for conforms ObservableObject?

class MyViewModel: ObservableObject {
    static let shared: MyViewModel = MyViewModel()
    
    @Published var result: String = ""
    
    private init() { }
    
    // some functions
}

struct ContentView: View {
    @ObservedObject private var vm = MyViewModel.shared
    
    var body: some View {
        Text(vm.result)
    }
}

Solution

  • Why do you think a viewmodel should be a singleton? And especially, why should an ObservableObject conformant class need a singleton instance? That's a bad idea.

    Not only is this absolutely unnecessary, this would also mean you cannot have several instances of the same view on the screen without them having shared state. This is especially bad on iPad if you want to support split screen and running 2 scenes of your app on the screen at the same time.

    Don't make anything a singleton, unless you absolutely have to.

    The only important thing to keep in mind with storing @ObservedObjects on SwiftUI Views is that they should never be initialised inside the view. When an @ObservedObject changes (or one of its @Published properties change), the View storing it will be reloaded. This means that if you create the object inside the View, whenever the object updates, the view itself will create a new instance of said object.

    So this is a bad idea and won't work:

    struct ContentView: View {
        // Never do this
        @ObservedObject private var vm = MyViewModel()
        
        var body: some View {
            Text(vm.result)
        }
    }
    

    Instead, you need to inject the viewmodel into your View (by creating it in the parent view or in a coordinator, etc, wherever you create your ContentView from).

    struct ParentView: View {
        @State private var childVM = MyViewModel()
    
        var body: some View {
            ContentView(vm: childVM)
        }
    }
    
    
    struct ContentView: View {
        @ObservedObject private var vm: MyViewModel
     
        // Proper way of injecting the view model
        init(vm: MyViewModel) {
            self.vm = vm
        }
       
        var body: some View {
            Text(vm.result)
        }
    }