#StateObject vs #ObservedObject when passed externally but owned by the view - ios

Based on this answer: What is the difference between ObservedObject and StateObject in SwiftUI
And the Apple documentation code here: https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
In SwiftUI app, a #StateObject property wrapper should be used when a View instantiates the object itself, so that the object won't be recreated during a view update.
If the object is instantiated somewhere else, an #ObservedObject wrapper should be used instead.
However, there is a fine line which makes it a bit unclear: what if the object is instantiated somewhere else, but "injected" to the View and then the view is the sole owner / holder of that object? Should it be #StateObject or #ObservedObject?
Sample code to get the point illustrated:
import SwiftUI
import Combine
import Foundation
struct ViewFactory {
func makeView() -> some View {
let viewModel = ViewModel()
return NameView(viewModel)
}
}
final class ViewModel: ObservableObject {
#Published var name = ""
init() {}
}
struct NameView: View {
// Should this be an `#ObservedObject` or `#StateObject`?
#ObservedObject var viewModel: ViewModel
init(_ viewModel: ViewModel) {
self.viewModel = viewModel
}
var body: some View {
Text(viewModel.name)
}
}
Based on this article: https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject
There is one important difference between #StateObject and #ObservedObject, which is ownership – which view created the object, and which view is just watching it.
The rule is this: whichever view is the first to create your object must use #StateObject, to tell SwiftUI it is the owner of the data and is responsible for keeping it alive. All other views must use #ObservedObject, to tell SwiftUI they want to watch the object for changes but don’t own it directly.
it appears that if the View to instantiate the ViewModel, it has to be declared with #StateObject. My code is very similar, the only difference is that the ViewModel is created elsewhere, but the View "owns" it after the initialization.

This is a really interesting question. There's some subtle behavior going on here.
First, notice that you can't just change #ObservedObject to #StateObject in NameView. It won't compile:
struct NameView: View {
#StateObject var viewModel: ViewModel
init(_ viewModel: ViewModel) {
self.viewModel = viewModel
// ^ 🛑 Cannot assign to property: 'viewModel' is a get-only property
}
...
}
To make it compile, you have to initialize the underlying _viewModel stored property of type StateObject<ViewModel>:
struct NameView: View {
#StateObject var viewModel: ViewModel
init(_ viewModel: ViewModel) {
_viewModel = .init(wrappedValue: viewModel)
}
...
}
But there's something hidden there. StateObject.init(wrappedValue:) is declared like this:
public init(wrappedValue thunk: #autoclosure #escaping () -> ObjectType)
So the expression given as an argument (just viewModel above) is wrapped up in a closure, and is not evaluated right away. That closure is stored for later use, which is why it is #escaping.
As you might guess from the hoops we have to jump through to make it compile, this is a weird way to use StateObject. The normal use looks like this:
struct NormalView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.name)
}
}
And doing it the weird way has some drawbacks. To understand the drawbacks, we need to look at the context in which makeView() or NormalView() is evaluated. Let's say it looks like this:
struct ContentView: View {
#Binding var count: Int
var body: some View {
VStack {
Text("count: \(count)")
NormalView()
ViewFactory().makeView()
}
}
}
When count's value changes, SwiftUI will ask ContentView for its body again, which will evaluate both NormalView() and makeView() again.
So body calls NormalView() during this second evaluation, which creates another instance of NormalView. NormalView.init creates a closure which calls ViewModel(), and passes the closure to StateObject.init(wrappedValue:). But StateObject.init does not evaluate this closure immediately. It stores it away for later use.
Then body calls makeView(), which does call ViewModel() immediately. It passes the new ViewModel to NameView.init, which wraps the new ViewModel in a closure and passes the closure to StateObject.init(wrappedValue:). This StateObject also doesn't evaluate the closure immediately, but the new ViewModel has been created regardless.
Some time after ContentView.body returns, SwiftUI wants to call NormalView.body. But before doing so, it has to make sure the StateObject in this NormalView has a ViewModel. It notices that this NormalView is replacing a prior NormalView at the same position in the view hierarchy, so it retrieves the ViewModel used by that prior NormalView and puts it in the StateObject of the new NormalView. It does not execute the closure given to StateObject.init, so it does not create a new ViewModel.
Even later, SwiftUI wants to call NameView.body. But before doing so, it has to make sure the StateObject in this NameView has a ViewModel. It notices that this NameView is replacing a prior NameView at the same position in the view hierarchy, so it retrieves the ViewModel used by that prior NameView and puts it in the StateObject of the new NameView. It does not execute the closure given to StateObject.init, and so it does not use the ViewModel referenced by that closure. But the ViewModel was created anyway.
So there are two drawbacks to the weird way in which you're using #StateObject:
You're creating a new ViewModel each time you call makeView, even though that ViewModel may never be used. This may be expensive, depending on your ViewModel.
You're creating the ViewModel while the ContentView.body getter is running. If creating the ViewModel has side effects, this may confuse SwiftUI. SwiftUI expects the body getter to be a pure function. In the NormalView case, SwiftUI is calling the StateObject's closure at a known time when it may be better prepared to handle side effects.
So, back to your original question:
Should it be #StateObject or #ObservedObject?
Well, ha ha, that's difficult to answer without seeing an example that's less of a toy. But if you do need to use #StateObject, you should probably try to initialize it in the ‘normal’ way.

Related

How can I use an observableobject class in an observableobject on SwiftUI?

I attached an image. Please see it.
As far as I know the "View" is only view. It's not controller.
So Im developing like Way 1.
But I faced a problem that how can I use observableobject in another observableobject?
I thought if I pass a parameter with the observableobject the problem will be clean.
But I think it is bad way..
So I thought way 2.
But the way is the "View" is not only view. It is view and controller.
So Im confused the way2 is bad way or not.
Which way is good way? and Im wondering other SwiftUI developers how to develop about this case.
Please advice me if you think there is better way than way1 & way2.
Summary
Q1. Way1 - How can I use observableobject in another observableobject? (singltone? like static shared)
Q2. Way2 - Is it correct way? (View = view + controller)
Q3. Your advice.
Env
Xcode 14.2
Swift 5.7.2
Here is the sample code for your question:
struct MainView: View {
#StateObject var mainVM = MainViewModel()
#ObservedObject var discoveryVM:DiscoveryViewModel
var body: some View {
ZStack {
ScrollView {
// if data changed in DiscoveryViewModel view will automatically upadte
ForEach(discoveryVM.showProfiles, id: \.self) { profile in
MyView(profile: Profile)
}
}
}
.onReceive(discoveryVM.$toggle) { status in
// change something in MainViewModel when toggle in DiscoveryViewModel
mainVM.toggle = status // change variable(rearly used)
mainVM.doSomething() // call fucntions
}
}
}
class MainViewModel: ObservableObject {
#Published var toggle: Bool = false
func doSomething() {
print("Do something")
}
}
class DiscoveryViewModel: ObservableObject {
#Published var data: [ProfileModel] = []
#Published var toggle: Bool = false
}
ObservableObjects are mostly used where there is nesting of views
SwiftUI is a new architecture, old design patterns don't really fit. In SwiftUI the View struct isn't the view in the MVC sense, that view layer (e.g. UIView/NSView objects) is generated automatically for us based diffing the View structs which are super fast efficient value types. View state is held in property wrappers like #State which essentially makes the value behave like an object, e.g. it has a "memory". You can pass this down to sub-Views as let for read access or #Binding var for write access, SwiftUI will track dependencies and call body on the affected View structs when necessary.
Normally in SwiftUI we only have a single ObservableObject that holds the model structs in #Published properties. Usually there are 2 singletons, one shared that loads from disk and another preview pre-loaded with test data for driving SwiftUI previews. This object is shared across View structs using .environmentObject
If you attempt to use multiple ObservableObject for view data instead of #State you'll run into major problems, actually the kind of consistency problems that SwiftUI's use of value types was designed to eliminate.

What is the difference between ObservedObject and StateObject in SwiftUI

If I have an ObservableObject in SwiftUI I can refer to it as an #ObservedObject:
class ViewModel: ObservableObject {
#Published var someText = "Hello World!"
}
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.someText)
}
}
Or as a #StateObject:
class ViewModel: ObservableObject {
#Published var someText = "Hello World!"
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.someText)
}
}
But what's the actual difference between the two? Are there any situations where one is better than the other, or they are two completely different things?
#ObservedObject
When a view creates its own #ObservedObject instance it is recreated every time a view is discarded and redrawn:
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
}
On the contrary a #State variable will keep its value when a view is redrawn.
#StateObject
A #StateObject is a combination of #ObservedObject and #State - the instance of the ViewModel will be kept and reused even after a view is discarded and redrawn:
struct ContentView: View {
#StateObject var viewModel = ViewModel()
}
Performance
Although an #ObservedObject can impact the performance if the View is forced to recreate a heavy-weight object often, it should not matter much when the #ObservedObject is not complex.
When to use #ObservedObject
It might appear there is no reason now to use an #ObservedObject, so when should it be used?
You should use #StateObject for any observable properties that you
initialize in the view that uses it. If the ObservableObject instance
is created externally and passed to the view that uses it mark your
property with #ObservedObject.
Note there are too many use-cases possible and sometimes it may be desired to recreate an observable property in your View. In that case it's better to use an #ObservedObject.
Useful links:
What’s the difference between #StateObject and #ObservedObject?
What’s the difference between #ObservedObject, #State, and #EnvironmentObject?
What is the #StateObject property wrapper?
Apple documentation did explain why initializing with ObservedObject is unsafe.
SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view.
The solution is StateObject.
At the same time, the documentation showed us how we should create data models in a view (or app/scene) when it can hold on to the truth, and pass it to another view.
struct LibraryView: View {
#StateObject var book = Book() // Hold on to the 1 truth
var body: some View {
BookView(book: book) // Pass it to another view
}
}
struct BookView: View {
#ObservedObject var book: Book // From external source
}
Even though pawello2222's answer have nicely explained the differences when the view itself creates its view model, it's important to note the differences when the view model is injected into the view.
When you inject the view model into the view, as long as the view model is a reference type, there are no differences between #ObservedObject and #StateObject, since the object that injected the view model into your view should hold a reference to view model as well, hence the view model isn't destroyed when the child view is redrawn.
class ViewModel: ObservableObject {}
struct ParentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
ChildView(viewModel: viewModel) // You inject the view model into the child view
}
}
// Even if `ChildView` is discarded/redrawn, `ViewModel` is kept in memory, since `ParentView` still holds a reference to it - `ViewModel` is only released and hence destroyed when `ParentView` is destroyed/redrawn.
struct ChildView: View {
#ObservedObject var viewModel: ViewModel
}
Here is an example to illustrate the difference.
Every time you click the Refresh button the StateObjectClass is recreated from scratch only for CountViewObserved. This means it's #Published count property gets the default value of 0 when this happens.
The difference between #StateObject and #ObservedObject is clear. The #StateObject version of the observed StateObjectClass preserves its state since it is never deinitted. The #ObservedObject version does not as it is recreated. So you should use #StateObject for the owner of an ObservableObject.
import SwiftUI
class StateObjectClass: ObservableObject {
enum ObserverType: String {
case stateObject
case observedObject
}
#Published var count = 0
let type: ObserverType
let id = UUID()
init(type: ObserverType) {
self.type = type
}
deinit {
print(#function, "type: \(type.rawValue) id: \(id)")
}
}
struct CountViewState: View {
#StateObject var state = StateObjectClass(type: .stateObject)
var body: some View {
VStack {
Text("#StateObject's count: \(state.count)")
Button("ADD 1"){
state.count += 1
}
}
}
}
struct CountViewObserved: View {
#ObservedObject var state = StateObjectClass(type: .observedObject)
var body: some View {
VStack {
Text("#ObservedObject's count: \(state.count)")
Button("Add 1") {
state.count += 1
}
}
}
}
struct ContentView: View {
#State private var count = 0
var body: some View {
VStack {
Text("Refresh CounterView's count: \(count)")
Button("Refresh") {
count += 1
}
CountViewState()
.padding()
CountViewObserved()
.padding()
}
}
}
#StateObject is a state of a given view, thus the instance of it is retained by SwiftUI across body updates. It is not retained though when running in Preview.
#ObservedObject on the other hand is just an object being observed by given View, thus is not retained by SwiftUI (it has to be retained outside of the View).
In other words - it looks like SwiftUI keeps a strong reference of #StateObject and unowned reference of #ObservedObject.
Retained vs non-retained source, Previews behavior source, around ~8:30.
The difference between let's say :
#ObservedObject var book: BookModel
And
#StateObject var book: BookModel
#ObservedObject does NOT own the instance book, its your responsibility to manage the life cycle of the instance..
But when you want to tie the life cycle of your observable object book to your view like in #State you can use #StateObject.
In this case SwiftUI will OWN the observable object and the creation and destruction will be tied to the view's life cycle
SwiftUI will keep the object alive for the whole life cycle of the view
This is great for expensive resources, you do not need to fiddle with onDisappear anymore to release resources.
This clarification is taken from WWDC2020 Data essentials in SwiftUI

SwiftUI MVVM Pass EnvironmentObject into ViewModel [duplicate]

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

SwiftUI - How to pass EnvironmentObject into View Model?

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

iOS SwiftUI: ObservableObject from parent to child

Problem:
Passing down the view model and modifying it from the children, won't refresh the parent.
What I want:
Whenever I modify the view model from the child view, the parent refresh too.
Problem description:
I'm looking for a way to pass down a ViewModel ObservedObject to some child views.
This is my struct:
struct Parent: View {
#ObservedObject viewModel: ViewModel
var body: some View {
NavigationView {
Form {
ScrollView(showsIndicators: false) {
Section {
ChildViewOne(model: viewModel)
}
Section {
ChildViewTwo(model: viewModel)
}
Section {
ChildViewThree(model: viewModel)
}
}
}
}
}
}
struct AnyChild: View {
#ObservedObject viewModel: ViewModel
var body: some View {
// some stuff
}
}
In this way, whenever I modify from the child views the view model, the children rebuild correctly, but the parent won't, and the scrollview does not resize correctly.
I would like to pass down to the children the viewModel like a Binding object, so the parent will refresh too.
But I don't know how to do. Any help?
Solution:
I had a specific problem with the Form when I've removed everything worked fine.
But still the correct solution as #Alladinian wrote is to use the #EnvironmentObject
Instead of passing the same model to each subview, you can use #EnvironmentObject instead which is specifically designed for this.
You essentially have to inject your ObservableObject in your parent view with something like this (typically in your appdelegate before presenting the parent view, or in your previews setup for live previewing):
Parent().environmentObject(ViewModel())
then you can automatically get a reference in each view with something like this:
struct AnyChild: View {
#EnvironmentObject var model: ViewModel
//...
}
and use it with your bindings
Here is a brief article about the use of environment
Finally, regarding why your solution is not working, you must consider a single source of truth (the observable object in your Parent) and a #Binding for each subview that will eventually change the state of this object. I hope that this makes sense...

Resources