zrfrank
zrfrank

Reputation: 2698

SwiftUI MVVM Binding

How to pass a @Binding into a ViewModel.

Let's consider a simple example,

struct TopView: View {
    @State var isPresented: Bool
    var body: some View {
        SubView(isPresented: $isPresented)
    }
}

struct SubView: View {
    @Binding var isPresented: Bool
}

Then, if we want to a MVVM pattern,

Is it safe to use @Binding in an ObservableObject?

For example, is it safe to write,

struct TopView: View {
    @State var isPresented: Bool
    var body: some View {
        SubView(model: SubViewModel(isPresented: $isPresented))
    }
}

struct SubView: View {
    @ObservedObject var model: SubViewModel
}


// In "SubViewModel.swift"

import Foundation
import Combine
import SwiftUI

public final class SubViewModel: ObservedObject {
    @Binding var isPresented: Bool
    
    public init(isPresented: Binding<Bool>) {
        self._isPresented = isPresented
    }
}

How would you do it? Similar including how to pass environmentObject into a view model?

Upvotes: 2

Views: 848

Answers (1)

lorem ipsum
lorem ipsum

Reputation: 29242

You are describing the basic use of an ObservableObject. @Binding and @State are only used in a View

The Apple SwiftUI Tutorials might be very helpful so you understand the SwiftUI concepts. https://developer.apple.com/tutorials/swiftui/

There are some basic mistakes in your code I mentioned the changes in the code below.

import SwiftUI

struct PassingBinding: View {
    var body: some View {
        TopView1()//Best Practice
        //TopView2()//Singleton Pattern
    }
}
///This is the best way to do it
///https://developer.apple.com/tutorials/swiftui/handling-user-input
struct TopView1: View {
    @StateObject var model: SubViewModel = SubViewModel()
    var body: some View {
        VStack{
            SubView1().environmentObject(model)
            
            Button(action: {
                self.model.isPresented.toggle()
            }, label: {
                Text("Toggle isPresented")
            })
        }
    }
}

struct SubView1: View {
    @EnvironmentObject var model: SubViewModel
    var body: some View {
        Text("SubView - isPresented == \(model.isPresented.description)")
    }
}
///Another way that has some specifc uses is to use a Singleton model
///https://developer.apple.com/documentation/swift/cocoa_design_patterns/managing_a_shared_resource_using_a_singleton
struct TopView2: View {
    @StateObject var model: SubViewModel = SubViewModel.shared
    var body: some View {
        VStack{
            SubView2()
            
            Button(action: {
                self.model.isPresented.toggle()
            }, label: {
                Text("Toggle isPresented")
            })
        }        }
}

struct SubView2: View {
    @StateObject var model: SubViewModel = SubViewModel.shared
    var body: some View {
        Text("SubView - isPresented ==  \(model.isPresented.description)")
    }
}

///This item can be Observed "ObservableObject" vs I am Observing this object "ObservedObject"
///https://developer.apple.com/documentation/combine/observableobject
public final class SubViewModel: ObservableObject {
    static let shared: SubViewModel = SubViewModel()//Singleton Pattern 
    
    @Published var isPresented: Bool = false //Initialize here this is the source for the View
    //@Binding and @State is are only used in View struct not in an ObservableObject.https://developer.apple.com/documentation/swiftui/binding
    
    
}
struct PassingBinding_Previews: PreviewProvider {
    static var previews: some View {
        PassingBinding()
    }
}

Upvotes: 2

Related Questions