What's the difference between #Environment and #EnvironmentObject in SwiftUI?
From what I found from the documents, #Environment seems to be meant to be used for global dependencies like ColorScheme. But I couldn't find any precise difference between them. For example, can they be used interchangeably?
Here is the notes I have prepared for myself. Could be useful,
#EnvironmentObject
Its similar like #ObservedObject
The model should conform to the ObservableObject protocol
We need to mark properties in this model as #Published to notify changes to view which actively using the object
The model object should be as class for sure
No need for default value, because it can read default value from environment. If object is not available in environment, app will crash.
Another major difference is, say we have 5 views(V1…V5), if we want to pass a object directly from V1 to V5 we could use #EnvironmentObject rather than #ObservedObject. Set data to be passed in V1 and retrieve it in V5(or wherever needed). Code will be much simple.
It will hold only one type of instance at same time environment.
Its purely based on views. If a parent view sets environment object all its child can make use of it. If another parent view set another env object, their child’s can make us of it. Eg: If you set environment object in your ContentView in SceneDelegate all its child views can make use of it.
#Environment
We can use this to get system related values like whether apps is running in light or dark mode, core data's managed object context, size classes, etc...
We need to provide proper keys to access its value, because it holds same datatype against multiple keys.
I want to add something to others' answer.
#Environment is value type but #EnvironmentObject is reference type.
You can only use a single instance of objects in #EnvironmentObject.If you add another instance of an object, it will replace the previous one.
But as, #Environment key value pair, just make sure key is different.
You just need to use #EnvironmentObject var object: Object to make an object retrieve the instance from the environment, and inject the instance by .environmentObject(Object())
On the other hand, there are many predefined #Environment system-managed environment values. You can also create custom one. It needs to be struct type and conforms to EnvironmentKey.
Here is an example,
struct SunlightKey: EnvironmentKey {
static var defaultValue: Double = 1.09
}
Then add it to the EnvironmentValues as an extension of it.
Here is a basic example
extension EnvironmentValues {
var sunlight: Double {
get { self[SunlightKey.self] }
set { self[SunlightKey.self] = newValue }
}
}
Then, use it like, #Environment(\.sunlight) var sunlight in view file and inject value by .environment(\.sunlight, 4.05)
Hope, this helps
#Enviroment gives you access to values of properties such as user settings (e.g colour scheme, layout direction etc.) or properties of the view such as EditMode or PresentationMode. #EnviromentObject is defined by you and available to all views and changes to it drive updates to your views.
Both of them #Environment and #EnvironmentObject are property wrappers while #Environment keeps values with predefined keys, #EnvironmentObject keeps not only predefined keys but also arbitrary values. For example if you need to keep information about your User object which includes name, age, gender etc. you need to use #EnvironmentObject, whereas if you would like to keep whether device is in dark or light mode, system local language, calendar preferences, edit mode it is great for using #Environment.
#Environment(\.locale) var locale: Locale
#EnvironmentObject var user: User // is an object where you keep user-related information
#Environment is a key/value pair, whereas #EnvironmentObject is just a value identified by its type. Both are variable storage property wrappers.
Related
From reading one user's answer to #ObservedObject model lifecycle?, where the user says
Every time this view is created, it instantiates a new instance of TestModel. SwiftUI views are really more like view descriptions which are created and destroyed a lot during your app lifecycle
But I also learned from this article https://blog.scottlogic.com/2020/01/28/Exploring-SwiftUI-3-View-Updates.html that SwiftUI checks if the newly computed view is different from the previous state before rendering it.
So, let's say that the ObservedObject gets recreated, how does that affect the view update? Does SwiftUI compare the previously created view with the new one to see if they are different before updating it, or does a ObservedObject recreation always leads to a new view update.
Hope I am making this question clear.
Always leads to a new view update. The reason is the View appears changed because the property for the object will almost certainly have a different pointer compared to last time.
It's like having a memory leak and should be avoided. I'm sure a future version will throw a compilation error if a mistake like #ObservedObject var obj = SomeObject() is present. Because the object is instantly dealloced so any async thing it is doing just stops or crashes.
I'm use to have a class for several complex subviews in a view. Each can encapsulate it's own state. SwiftUI will let you refactor a long view function into subviews but I have not found anything other than passing #Binding variables through the views.
I keep ending up with a lot of state variables on the main view. Ideally what I would like would be if I could reference a subview of the main and directly set it's state like this
mainView.bottomPane.odometer.speed = 55
Is this possible? If not are there other ways around large chunks of State variables in the main form?
I saw in some article that #state has many limitations
that we shouldn't use it with complex models and its preferable to use it with the simple property like string, bool, ... etc.
and we should use it inside the view itself.
I tried to make a struct model and mark it with #state in the contentview (ParentView) and pass this model to its child views by wrapping it with #binding it worked just fine,
so i don't understand why we still need #objectbinding as we can pass the same value to these child views and if one change the others will change also? or what are the limitations of #state that #objectbinding solve?
I recommend you watch WWDC 2019 session: Data Flow in SwiftUI. It is very well explained. It describes in which scenarios #State is perfectly acceptable and where ObjectBinding/EnvironmentObject is necessary instead. The session is only 37 minutes long, but it will be a before and after in your understanding of bindings. Please do watch it, it will save time in the long run.
It all comes down to understanding where is the "source of truth" of your data. This is a concept that is also explained in the video. In a few words, the source of truth is were your data is born. If the data of your variable can be derived from some other variable, then it is not a source of truth.
What are the difference between #State and #BindableObject?
#State: It is local to the view. It has to be a value-type (e.g., a struct, Int, String, Array, etc) and its storage is managed by the framework.
#BindableObject: It is external to the view, it is a reference value (e.g., a class) and its storage is managed by you, giving you more flexibility to implement your own logic.
Note that #State variables are also great while prototyping your app. For example, if you are working on the layout of your view, you can initially use a #State variable to make things easier. Once your view does what you want, you can concentrate on creating your #BindableObject and replace your #State.
So, I have a view controller in which I have a UISegmentedControl connected to do a choice.
However, I want to set that UISegementedControl to unselected only if that view controller is accessed by pressing the tab bar to go to it. If it is accessed any other way, it can perform the standard viewWillAppear function that I have set, but only if it is accessed through the tab bar controller, I want the selection to be empty.
How would this be done?
Define a class variable in your controller, for e.g. isShownFromTabBar with true as the default value.
If you are showing the controller from a segue or from a code, set this variable to false.
I want to implement reordering of collection view cells in a collection view inside a View Controller, but the moveItemAt method is not being called since I can't seem to access the installsStandardGestureForInteractiveMovement property of the CollectionViewController.
I want to know if this property is exclusive to UICollectionViewController or I can somehow access it in UIViewController currently hosting it?