In the following example, I have an app with two Tab Views, Parent View 1 and Parent View 2, and each parent view has child views. All of the views share a view model (cDViewModel) that handles all of the Core Data related stuff. I have read that when passing data around views you should instantiate your object with #StateObject and then pass it around to other child views using #ObservedObject, clear enough. My confusion is that in my case almost all views in the app will be using the cDViewModel. In my app, I'm currently using Option 1 but I for some reason would like to adopt Option 2 if possible.
Does it make a difference which of the two methods below you use when sharing a common object within an MVVM app?
Option 1
This is how I'm currently using it in my app. Please note that I'm declaring the #StateObject inside the TabView section and start sharing it from there, in other words in this scenario only one instance of the cDViewModel is created.
struct MainView: View {
#StateObject var cDViewModel = CoreDataViewModel()
#State var selectedView = 0
var body: some View {
TabView(selection: $selectedView){
ParentView1(cDViewModel: cDViewModel)
.tabItem {
Text("Parent View 1")
}.tag(0)
ParentView2(cDViewModel: cDViewModel)
.tabItem {
Text("Parent View 2")
}.tag(1)
}
}
}
First Tab View
struct ParentView1: View {
#ObservedObject var cDViewModel:CoreDataViewModel
NavigationLink(destination: ChildView1(cDViewModel: cDViewModel)){ }
}
struct ChildView1: View {
#ObservedObject var cDViewModel:CoreDataViewModel
NavigationLink(destination: OtherChildView(cDViewModel: cDViewModel)){ }
}
// Other Child views...
Second Tab View
struct ParentView2: View {
#ObservedObject var cDViewModel:CoreDataViewModel
NavigationLink(destination: ChildView2(cDViewModel: cDViewModel)){ }
}
struct ChildView2: View {
#ObservedObject var cDViewModel:CoreDataViewModel
NavigationLink(destination: OtherChildView(cDViewModel: cDViewModel)){ }
}
// Other Child views...
Option 2
Please note that here I'm declaring the #StateObject in each of the parent views and not in the TabView section, for some reason I tend to like this option better but I'm not sure if this could create refreshing issues by having multiple #StateObject declarations.
struct MainView: View {
#State var selectedView = 0
var body: some View {
TabView(selection: $selectedView){
ParentView1()
.tabItem {
Text("Parent View 1")
}.tag(0)
ParentView2()
.tabItem {
Text("Parent View 2")
}.tag(1)
}
}
}
First Tab View
struct ParentView1: View {
#StateObject var cDViewModel = CoreDataViewModel()
NavigationLink(destination: ChildView1(cDViewModel: cDViewModel)){ }
}
struct ChildView1: View {
#ObservedObject var cDViewModel:CoreDataViewModel
NavigationLink(destination: OtherChildView(cDViewModel: cDViewModel)){ }
}
// Other Child views...
Second Tab View
struct ParentView2: View {
#StateObject var cDViewModel = CoreDataViewModel()
NavigationLink(destination: ChildView2(cDViewModel: cDViewModel)){ }
}
struct ChildView2: View {
#ObservedObject var cDViewModel:CoreDataViewModel
NavigationLink(destination: OtherChildView(cDViewModel: cDViewModel)){ }
}
// Other Child views...
I think this implementation is a slightly misguided attempt at achieving an MVVM-like architecture (trust me, I've been there).
In MVVM the so-called 'view model' is just as it sounds - a model for each view. It is the object that is responsible for keeping track of state for its view. If a user taps a button, the view model would be notified of the interaction, it does some internal recalculating of state, and then publishes that new state for the view to consume and implement. An example of this might be...
struct MyView: View {
#StateObject private var viewModel = MyViewViewModel()
var body: some View {
VStack(spacing: 20) {
Button("Hello") {
viewModel.helloTapped()
}
if viewModel.helloIsVisible {
Text("Why hello friend.")
}
}
}
}
class MyViewViewModel: ObservableObject {
#Published private(set) var helloIsVisible = false
func helloTapped() {
helloIsVisible = true
}
}
In this example, the view model holds the state of the view (helloIsVisible) and publishes it for the view to consume. When the user interacts with the button, the view model recalculates state.
The flaws I see in your implementation are as follows:
You seem to be combining the view model with what should be another object in the service layer of your app. Your view and view model should be completely dumb about how the data that drives your view (hopefully in the form of a concise model) were fetched from CoreData. You can delegate the task of fetching the data and packaging it into a consumable model for the view model into another object (another class) in the service layer of your app. This could be passed around the app in a number of ways, preferably as a dependency, but also perhaps instantiated as a public singleton. It would be wise to read up on the different techniques of making this service available. The view model would either ask this 'CoreData fetching' class for the data that it needs, or the data would be injected as a dependency. Read about singletons here and dependency injection here.
Each view should have its own view model. Presumably the views are different in some way - otherwise, why have more than one? Create a separate view model for each, that each uniquely manage the state of its corresponding view. They should each have a similar mechanism for retrieving data from the 'CoreData fetching' class.
I have the following viewfile:
import SwiftUI
class Progress: ObservableObject {
#Published var index = 0
}
struct RegistrationView: View {
#ObservedObject var progress: Progress
var body: some View {
TabView(selection: $progress.index) {
setUsernameView(progress: self.progress).tabItem{}.tag(0)
setMasterPasswordView().tabItem{}.tag(1)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
}
}
when I am finished with my setUserNameView() I want to update index to 1 so that it switches to setMasterPasswordView. Im passing progress to the view and then inside to another view which updates the value.
Now xcode complains: Missing argument for parameter 'progress' in call the call is in the contentView().
import SwiftUI
struct ContentView: View {
var body: some View {
RegistrationView()
}
}
My question is, why does the view or any other view above RegistrationView need this object too and how do I save me from that work. I tried to make progress private but that doesn't work. I am defining the class and all other things in this view and I only need it for itself and the child of a child.
P.S. is there a better methode for observable objects? For example when I need the value in a main view and can change it in a child.child.child.view I have to pass it all the way down. And that seems kind of unnecessary work.
If you're not going to use it in parent view then just use locally as StateObject
struct RegistrationView: View {
#StateObject var progress = Progress() // << here !!
// ... other code
I have a view called PurchaseView. This view displays details about the purchase, what was purchased and who purchased it. What I'm doing is that in this view im putting both the ItemView and ClientView inside PurchaseView. ItemView and ClientView are shared and there are used in other parts of my app. They have their own ViewModels.
I have also tried to put ItemViewModel and ClientViewModel inside PurchaseViewModel but I do not know if it is ok to put an ObservableObject inside another ObservableObject. Is this a good approach or there should not be any ObservableObject inside an ObservableObject? Which one of the following is better?
This?
class PurchaseViewModel: ObservableObject {
let clientViewModel: ClientViewModel
let itemsViewModel: ItemViewModel
//
}
Or this?
struct PurchaseView: View {
#ObservedObject var purchaseViewModel: PurchaseViewModel
#ObservedObject var itemViewModel: ItemViewModel
#ObservedObject var clientViewModel: ClientViewModel
var body: some View {
//
}
}
Purchase model:
class Purchase {
let id: String
let total: Double
// ...
var item: Item?
var client: Client?
}
Your first solution will not work as the changes to the nested ObservableObjects aren't propagated upwards:
class PurchaseViewModel: ObservableObject {
let clientViewModel: ClientViewModel
let itemsViewModel: ItemViewModel
...
}
A workaround can be found here: How to tell SwiftUI views to bind to nested ObservableObjects.
Your second approach is right and will work for most cases:
struct PurchaseView: View {
#ObservedObject var purchaseViewModel: PurchaseViewModel
#ObservedObject var itemViewModel: ItemViewModel
#ObservedObject var clientViewModel: ClientViewModel
...
}
If you share an ObservableObject for many views you can inject it to the environment instead and access as an #EnvironmentObject.
Alternatively you can make your nested ObservableObjects to be structs:
class PurchaseViewModel: ObservableObject {
#Published var clientViewModel: ClientViewModel // <- add `#Published`
...
}
struct ClientViewModel { // <- use `struct` instead of `class`
...
}
Note that your ClientViewModel will become a new struct every time it (or any of its properties) changes - this solution shouldn't be overused (especially for complex ViewModels).
Just noting that I'm using the NestedObservableObject approach from How to tell SwiftUI views to bind to nested ObservableObjects.
Normally I'd avoid this but the nested object in question is actually a CoreData model so breaking things out into smaller views doesn't really work in this regard.
Since the world treats NSManagedObjects as (mostly) ObservableObjects, this solution seemed best.
I've been newly studying SwiftUI.
And since I've seen Data flow over SwiftUI video from Apple explaining difference between #ObjectBinding and #EnvironmentObject, a question has come to my mind.
What does apple mean by :
You have to pass around the model from hop to hop in #ObjectBinding ? (29':00")
Do we have to pass the object using #binding in another views for using them ?
What if we don't use #binding and reference to it using another #ObjectBinding ?
Does that make an inconvenience or make SwiftUI not to work correctly or views not being sync with each other ?
[Edit: note that #ObjectBinding is no longer around; instead you can use #State to mark an instance variable as requiring a view refresh when it changes.]
When a view declares an #State or #Binding variable, its value must be explicitly passed from the view's parent. So if you have a long hierarchy of views with some piece of data from the top being used in the bottom, you must code every level of that hierarchy to know about and pass down the data.
In his comment at 29:00, he is contrasting this to using #EnvironmentVariable in a child view, which searches the whole hierarchy for that piece of data. This means any views that do not explicitly need the data can effectively ignore it. Registering a variable needs only be done once (via .environmentObject(_) on a view).
Here is a contrived example. Given some data type conforming to ObservableObject,
class SampleData: ObservableObject {
#Published var contents: String
init(_ contents: String) {
self.contents = contents
}
}
Consider this view hierarchy:
struct ContentView: View {
#State private var data = SampleData("sample content")
var body: some View {
VStack {
StateViewA(data: self.data)
EnvironmentViewA()
.environmentObject(self.data)
}
}
}
struct StateViewA: View {
#State var data: SampleData
var body: some View {
StateViewB(data: self.data)
}
}
struct StateViewB: View {
#State var data: SampleData
var body: some View {
Text(self.data.contents)
}
}
struct EnvironmentViewA: View {
var body: some View {
EnvironmentViewB()
}
}
struct EnvironmentViewB: View {
#EnvironmentObject var data: SampleData
var body: some View {
Text(self.data.contents)
}
}
The result in ContentView will be two views that display the same piece of text. In the first, StateViewA must pass the data on to its child (i.e. the model is passed from "hop to hop"), whereas in the second, EnvironmentViewA does not have to be aware of the data at all.
I'm looking to create an EnvironmentObject that can be accessed by the View Model (not just the view).
The Environment object tracks the application session data, e.g. loggedIn, access token etc, this data will be passed into the view models (or service classes where needed) to allow calling of an API to pass data from this EnvironmentObjects.
I have tried to pass in the session object to the initialiser of the view model class from the view but get an error.
how can I access/pass the EnvironmentObject into the view model using SwiftUI?
You can do it like this:
struct YourView: View {
#EnvironmentObject var settings: UserSettings
#ObservedObject var viewModel = YourViewModel()
var body: some View {
VStack {
Text("Hello")
}
.onAppear {
self.viewModel.setup(self.settings)
}
}
}
For the ViewModel:
class YourViewModel: ObservableObject {
var settings: UserSettings?
func setup(_ settings: UserSettings) {
self.settings = settings
}
}
You shouldn't. It's a common misconception that SwiftUI works best with MVVM. MVVM has no place in SwiftUI. You are asking that if you can shove a rectangle to fit a triangle shape. It wouldn't fit.
Let's start with some facts and work step by step:
ViewModel is a model in MVVM.
MVVM does not take value types (e.g.; no such thing in Java) into consideration.
A value type model (model without state) is considered safer than reference type model (model with state) in the sense of immutability.
Now, MVVM requires you to set up a model in such way that whenever it changes, it updates the view in some pre-determined way. This is known as binding.
Without binding, you won't have nice separation of concerns, e.g.; refactoring out model and associated states and keeping them separate from view.
These are the two things most iOS MVVM developers fail:
iOS has no "binding" mechanism in traditional Java sense. Some would just ignore binding, and think calling an object ViewModel automagically solves everything; some would introduce KVO-based Rx, and complicate everything when MVVM is supposed to make things simpler.
Model with state is just too dangerous because MVVM put too much emphasis on ViewModel, too little on state management and general disciplines in managing control; most of the developers end up thinking a model with state that is used to update view is reusable and testable. This is why Swift introduces value type in the first place; a model without state.
Now to your question: you ask if your ViewModel can have access to EnvironmentObject (EO)?
You shouldn't. Because in SwiftUI a model that conforms to View automatically has reference to EO. E.g.;
struct Model: View {
#EnvironmentObject state: State
// automatic binding in body
var body: some View {...}
}
I hope people can appreciate how compact SDK is designed.
In SwiftUI, MVVM is automatic. There's no need for a separate ViewModel object that manually binds to view which requires an EO reference passed to it.
The above code is MVVM. E.g.; a model with binding to view. But because model is value type, so instead of refactoring out model and state as view model, you refactor out control (in protocol extension, for example).
This is official SDK adapting design pattern to language feature, rather than just enforcing it. Substance over form. Look at your solution, you have to use singleton which is basically global. You should know how dangerous it is to access global anywhere without protection of immutability, which you don't have because you have to use reference type model!
TL;DR
You don't do MVVM in java way in SwiftUI. And the Swift-y way to do it is no need to do it, it's already built-in.
Hope more developer see this since this seemed like a popular question.
Below provided approach that works for me. Tested with many solutions started with Xcode 11.1.
The problem originated from the way EnvironmentObject is injected in view, general schema
SomeView().environmentObject(SomeEO())
ie, at first - created view, at second created environment object, at third environment object injected into view
Thus if I need to create/setup view model in view constructor the environment object is not present there yet.
Solution: break everything apart and use explicit dependency injection
Here is how it looks in code (generic schema)
// somewhere, say, in SceneDelegate
let someEO = SomeEO() // create environment object
let someVM = SomeVM(eo: someEO) // create view model
let someView = SomeView(vm: someVM) // create view
.environmentObject(someEO)
There is no any trade-off here, because ViewModel and EnvironmentObject are, by design, reference-types (actually, ObservableObject), so I pass here and there only references (aka pointers).
class SomeEO: ObservableObject {
}
class BaseVM: ObservableObject {
let eo: SomeEO
init(eo: SomeEO) {
self.eo = eo
}
}
class SomeVM: BaseVM {
}
class ChildVM: BaseVM {
}
struct SomeView: View {
#EnvironmentObject var eo: SomeEO
#ObservedObject var vm: SomeVM
init(vm: SomeVM) {
self.vm = vm
}
var body: some View {
// environment object will be injected automatically if declared inside ChildView
ChildView(vm: ChildVM(eo: self.eo))
}
}
struct ChildView: View {
#EnvironmentObject var eo: SomeEO
#ObservedObject var vm: ChildVM
init(vm: ChildVM) {
self.vm = vm
}
var body: some View {
Text("Just demo stub")
}
}
Solution for: iOS 14/15+
Here's how you might interact with an Environment Object from a View Model, without having to inject it on instantiation:
Define the Environment Object:
import Combine
final class MyAuthService: ObservableObject {
#Published private(set) var isSignedIn = false
func signIn() {
isSignedIn = true
}
}
Create a View to own and pass around the Environment Object:
import SwiftUI
struct MyEntryPointView: View {
#StateObject var auth = MyAuthService()
var body: some View {
content
.environmentObject(auth)
}
#ViewBuilder private var content: some View {
if auth.isSignedIn {
Text("Yay, you're all signed in now!")
} else {
MyAuthView()
}
}
}
Define the View Model with methods that take the Environment Object as an argument:
extension MyAuthView {
#MainActor final class ViewModel: ObservableObject {
func signIn(with auth: MyAuthService) {
auth.signIn()
}
}
}
Create a View that owns the View Model, receives the Environment Object, and calls the appropriate method:
struct MyAuthView: View {
#EnvironmentObject var auth: MyAuthService
#StateObject var viewModel = ViewModel()
var body: some View {
Button {
viewModel.signIn(with: auth)
} label: {
Text("Sign In")
}
}
}
Preview it for completeness:
struct MyEntryPointView_Previews: PreviewProvider {
static var previews: some View {
MyEntryPointView()
}
}
I choose to not have a ViewModel. (Maybe time for a new pattern?)
I have setup my project with a RootView and some child views. I setup my RootView with a App object as the EnvironmentObject. Instead of the ViewModel accessing Models, all my views access classes on App. Instead of the ViewModel determining the layout, the view hierarchy determine the layout. From doing this in practice for a few apps, I've found my views are staying small and specific. As an over simplification:
class App: ObservableObject {
#Published var user = User()
let networkManager: NetworkManagerProtocol
lazy var userService = UserService(networkManager: networkManager)
init(networkManager: NetworkManagerProtocol) {
self.networkManager = networkManager
}
convenience init() {
self.init(networkManager: NetworkManager())
}
}
struct RootView: View {
#EnvironmentObject var app: App
var body: some View {
if !app.user.isLoggedIn {
LoginView()
} else {
HomeView()
}
}
}
struct HomeView: View {
#EnvironmentObject var app: App
var body: some View {
VStack {
Text("User name: \(app.user.name)")
Button(action: { app.userService.logout() }) {
Text("Logout")
}
}
}
}
In my previews, I initialize a MockApp which is a subclass of App. The MockApp initializes the designated initializers with the Mocked object. Here the UserService doesn't need to be mocked, but the datasource (i.e. NetworkManagerProtocol) does.
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
Group {
HomeView()
.environmentObject(MockApp() as App) // <- This is needed for EnvironmentObject to treat the MockApp as an App Type
}
}
}
The Resolver library does a nice job to get dependency injection for model classes. It provides a property wrapper #Injected which is very similar in spirit to #EnvironmentObject but works everywhere. So in a model, I would inject a ExampleService like this:
class ExampleModel: ObservableObject {
#Injected var service: ExampleService
// ...
}
This can also be used to resolve dependencies for Views:
struct ExampleView: View {
#ObservedObject var exampleModel: ExampleModel = Resolver.resolve()
var body: some View {
// ...
}
}
An alternative for Views is to use #EnvironmentObject in the SwiftUI view hierarchy, but this gets a little bit cumbersome because you'll have two dependency-injection containers, Resolver/#Injected for everything that's app-wide/service-like and SwiftUI/#EnvironmentObject in the view hierarchy for everything that relates to views/for view models.
Simply create a Singleton and use it wherever you want (view / class / struct / ObservableObject ...)
Creating Class should look like this:
class ApplicationSessionData
{
// this is the shared instance / local copy / singleton
static let singleInstance = ApplicationSessionData()
// save shared mambers/vars here
var loggedIn: Bool = false
var access: someAccessClass = someAccessClass()
var token: String = "NO TOKET OBTAINED YET"
...
}
Using Class/Struct/View should look like this:
struct SomeModel {
// obtain the shared instance
var appSessData = ApplicationSessionData.singleInstance
// use shared mambers/vars here
if(appSessData.loggedIn && appSessData.access.hasAccessToThisView) {
appSessData.token = "ABC123RTY..."
...
}
}
You need to be aware of the pitfalls that exist in Singletons, so you won't fall into one.
Read more here: https://matteomanferdini.com/swift-singleton