SwiftUI Part 2 (DataFlow in SwiftUI — 1 )
As shown in Figure 1 , we have Text stlyle rendering is based on some condition if selected == true we will make the background color of Text red.
As shown in Figure 2 we want some user interactive so that when the user taps on it, would toggle the selected
property And we can do that by using a button. A button takes some content and an action to execute when the user taps on it. In the action, we just toggle selected
. But if we go to build a run, we get a compiler error.
But this is good because it’s keeping us on the right path and through to one of the general principles of UI. We don’t mutate the view hierarchy.
Every time your UI is updated, it’s because some view body is generating a different value. And in order to handle case like this, we have a tool that’s called State
.
As shown in Gif2 by using @State
property wrapper on the selected
property. And by doing this, we are telling the system that selected
is a value that can change over time and that the ContentView
depends on it.
Now if we build a run, we won’t get a compiler error. And when the user taps on the button, the state value changes and the framework will generate a new body for this view. If you’re not familiar with Property Wrapper, it’s a powerful new feature in Swift 5.1.
All you need to know is that when you add the Property Wrapper, you’re wrapping this property and augment it with some additional behavior when it’s read or written. Note Property wrapper can only be applied to a ‘var’
- When you
@State
the framework, allocate the persistent storage for the variable on the view we have and track it as a dependency of a view means when we mutate system knows and recalculate/re-render the View- If the system is creating storage for you, you always have to specify an initial constant value.
- View can be recreated often by the system, but with state, the framework knows that it needs to persist the storage across multiple update of the same view
- It’s a good practice to explicitly mark state property as private to really enforce the idea that state is owned and managed by that view specifically.
Terminology
Source Of Truth →
Remember when we define some State
, the framework is allocating persistent storage for you. When framework allocate storage it’s means we create Source of truth , Here selected
is a soure of truth . you should always have single source of truth except few cases. And it’s a good practice to explicitly mark state property as private to really enforce the idea that state is owned and managed by that view specifically. So in short @State
var you are define a source of truth
How SwiftUI View Update Change →
When we tap on Button, we mutated selected
state, since selected property is @State
and the state changes so Swift UI starting validating their view that owns the state, which mean it will recompute the body of that view and all of its children. In our case View is ContentView,
As shown in Fugure we only one one funnel to render View , So how that funnel trigger when your State variable change. These all thing happen automatically so no UI inconistency
Note all the changes always flow down through your view hierarchy.And we’re able to do these very efficiently because the framework is comparing the view and rendering again only what is changed.
Framework manages the dependency →
You will find this term alot it actually means you define View dependeny using State or some other tools which we talk later and when there is a mutation it will re-render view so we just declare Vew dependency and framework automatically manages it
SwiftUI views are a function of state →
view are a function of state, not a sequence of event. Traditionally, you respond to some event by directly mutating your view hierarchy. For example, by adding or removing a subview or changing the alpha Like in UIkit when there is a button tap if we need to hide some view we directly access view and change it’s hidden property . Whereas, in SwiftUI, you mutate some state and the state work as a source of truth from which you derive your view. This is where SwiftUI, declarative syntax shine. In this case SwiftUI is doing this thing
State
- The intuitively named
@State
allows you to monitor and read a given persistent value automatically within a local given view - Every
@State
is a persistent source of truth for a given value, managed by the framework. - Represent Source of Truth
- It’s best suited to simple value types (e.g. Bool, Int, or String) with an intended lifetime scope of the view in which you’re declaring it.
- Represent View local data
As shown in Figure 3 Property wrapper can only be applied to a ‘var’
As shown in Gif 2 we want selected poperty to refer by the StylishText
View as well , as you can see we created two State property so it means two storage of same property. Here we created Two source of truth As per apple Swift UI best practice “Regardless of where the source of truth live, you should always have a single source of truth.” Duplicate source of truth can lead to synchronisation problem which you see clearly in the gif
Note: SwiftUI are declarative data dependency (means you just tell your dependency everything done by framework like expert). There is no manual synchronization or invalidation. With SwiftUI, you simply describe the dependency to the framework using few tools and the frameworks handles all the rest. And that means that you can focus on building the best experience for your user. Remember part1 under Benefit section (Remove all UI inconsistencies, Declarative UI update, There is only one tunnel / path where you can update your UI with these tool we achieve these benefits)
As you can see we want that there should be one selected
property and it should be pass to subviews and when there is change in property, subviews also reflect/render their UI. In short we want single source of truth when it value change all sbviews will know as well who has this data as dependency . Currently in this code By using @State
in SubView, we’ve created another source of truth for selected
, which we have to keep in sync with the state in the parent ContentView
. And that’s not what we want. When you run with this Subview have it state and parent have it state so we will not get the consistent UI
Binding
What we want is to make this a StylishText
reusable component.
So this view shall not own a source of truth, it just need to be able to read a value and mutate it. But it doesn’t need to own state. And we have tools for this job. It’s called Binding
.
By using the Binding
Property Wrapper, you define an explicit dependency to a source of truth without owning it.Additionally, you don’t need to provide an initial value because the binding can be derived from a parent State
. So As you can see we have one source of truth now and when I tap both UI will update. Rember when we tap state change swiftUI see effiectly which View this data dependency then all dependent view and its child view re-render. ContentView
is still holding the state. That’s your source of truth. From the state, you can derive a binding by using the dollar prefix on the property name. This is your way to allow a component to access your state via binding.The dollar prefix is another feature of property wrapper. To use @State
effectively, you should limit its use if possible, and use the derived binding or value instead.
Note:
@State
binding variables are only in memory for the lifetime of the view. When the View in which they’re declared is no longer available, the@State
binding values also become unavailable automatically. This frees up precious memory when the value is no longer needed. By using Binding we define explicit dependency to a source of truth without owning it . Additonaly you don’t need to provide inital value beacuse Binding are derived from state
As shown we did with whole Struct
since Struct
is value type as well @State
and Binding
will work , Now any change in struct will manage by framework
As shown in Figure 6, child subview using binding can read value and it can reflect to parent View re-render as well, This is the power of Binding. Binding allow read/write access to source of truth (read/write means it can update the source of truth as well)
As shown in Figure 7, currently we pass Foo
struct property as Binding
and it is working as well, we have one source of truth and we pass name of struct as binding and in child view we can mutate this specfic property as well
As shown in Figure 8, what we did we basically change the Foo type from struct
to class
and bang and you can see when you toggle you will not see any change in the background color of Hello world text. @State
properties are only useful for value types and since for class by changing internal property doesn’t change it’s reference it will not able to identify there is a change
@State
properties are only useful for value types likeString
,Int
,Bool
,struct
andenum
. If you want the view to own a reference type, such as a class, you use@ObservedObject
or@EnvironmentObject
or@StateObject
instead. You’ll learn about these later in this part.
Now it will work since we are changing reference of foo object , Now if you toggle it will work. But from code you will thinking SwiftUI should have another tool to cater this and yes you are right
As shown in Figure 10 , if we pass foo which is class type to another reusable view , if that view change the reference property by assigning new object it will work, but it’s not a good approach as discussed above
Last example here we created a TextField which take Binding
(Note we source of truth is owned by us ) and When the user enters a name into the TextField control, the value will be updated within the state object and the view will be declared invalid and rebuilt.
The
@Binding
is a two-way relationship with the TextField: When you modified the value in your TextField, it retained a reference to the state wrapper — and the value of your@State
object is changed without the need for any additional code. Note use@State
when you want read/write and if you only want read access use swift normal property. After all, the@State
variable does add an additional, although minor, footprint to variables, so using them with due consideration is important.
In Short
The intuitively named
@State
allows you to monitor and read a given persistent value automatically within a local given view. Every@State
is a persistent source of truth for a given value, managed by the framework. It’s best suited to simple value types (e.g. Bool, Int, or String) with an intended lifetime scope of the view in which you’re declaring it.By adding@State
we are adding View dependency and framework will create peristsant storage and when state value mutate framework will compute View Body .@State
binding variables are only in memory for the lifetime of the view. When the View in which they’re declared is no longer available, the @State binding values also become unavailable automatically. This frees up precious memory when the value is no longer needed.
To use@State
effectively, you should limit its use if possible, and use the derived binding or value instead
OR
We just say that when — that when we define some
@State
, the framework is allocating persistent storage for you. One was the special property of state variable is that SwiftUI kind of start when they change. And because SwiftUI knows that the state variable was writing the body, it knows that the view rendering depend on that@State
variable.
When the user interact with the button, the framework execute its action which in turn will meet at some state. The runtime the data — the state does change and starting validating their view that owns the state, which mean it will recompute the body of that view and all of its children. In this sense, all the changes always flow down through your view hierarchy. And we’re able to do these very efficiently because the framework is comparing the view and rendering again only what is changed. This is exactly what we mentioned earlier when we say that the framework manages the dependency for you.
As shown in Figure 12 we created initlizer and pass @Binding
, it will be helpful for you to learn syntax to pass binding , You were thinking “(let) is more appropriate for the foo However, remembering that the @Binding
attribute wraps answered into a manager object with a bound value, you need to allow it to be mutable. The value may not change, but the @Binding
state management object that you’re passing into the initializer must be allowed to in order to notify the origin @State
object when a change has occurred. If you do try and make a @Binding
object a constant, it will result in two errors: one at the declaration (“Property wrapper can only be applied to a var”), and when you try and set it during initialization (“Value of type StylishText has no member foo”).“@Binding
is a first-class reference to data and is great for reusability. You can access the value of the binding in the same way as you’d access a @State
value — by using the $ prefix operator. When setting it in the initializer, the optimal method is to prefix the variable name with an underscore, as illustrated above, as that’s the true internal name of your binding. In other words, foo is your state wrapper and _foo is your state value. If you try and set foo to an Foo type, you’ll receive an error notifying you that foo is a Binding<Foo> and not an Foo, so coercion is impossible.
Note: Currently foo variabe is in ContentView as State (source of truth) and in StylishText as Binding , now when foo is change in StylishText it will ask it source of truth which is in ContentView to re-render and same for when foo is change in
ContentView
it will rebuild theStylishText
.
As shown in Figure 13 we have now ContentView
which has state foo and this foo variable pass as a binding to child view, so any child write foo
variable it will renreder all views who have this property either source of truth or Binding . In this case we update foo value from StylishText
view and it rerender StylishText
, StylishText1
and ContentView
, so in short @State
and @Binding
make your value type variable as a reference internally and it totally manage by framework so no worry,
As shown in Gif 1
- When we launch app it render ContentView and it subviews
- Then we toggle state previously name value “ali” and we change it to “” so framework will now there is change in View dependency it will renrender ContentView and it subviews (subview rendering will be efficent as well it will use some cache info )
- Third we agains change value from empty to empty framework know no change so it will not render UI at all
Rule 1 → Every @State is a source of truth
Rule 2→ View is a function of State not a Sequence of State
Rule 3→ Swift UI drive your View using some State as dependency (Declarative approach)
Rule 4 → Reusable Component don’t own source of truth of it’s parent
Observing Objects
In previous section we work on @State
which apply on Value type and it’s local to View , As shown in Figure 14 we previoulsy face this case when we make Foo
as class
it doesn’t know to update automatically To update the view when foo
changes, you need to observe it. Adding the appropriate property wrapper helps you manage the UI update with the data change.
As shown in Figure 15 few things to understand , Some concept will be from new Combine framework
- The
ObservableObject
conformance allows instances of thisclass
to be used inside views, so that when important changes happen the view will reload.It’s a class constrained protocol which means it can only be adopted by reference type. @Published
property wrapper is added to any properties inside an observed object that should cause views to update when they change In short . This is a simple way of sending notifications to subscribers of state objects in SwiftUI. In this case, you’re using it to notify subscribers that the name has been updated on foo object which is reference type.- @ObservedObject is an attribute that implements the
ObservableObject
protocol in a manner that it provides a way to create state-managed objects. @ObservedObject makes use of Combine to send updates to all subscribers (or bound views). In shortObservedObject
is a property wrapper that you can use to annotate reference type properties of your view that holds a type conforming toObservableObject
. Using it informs SwiftUI to start tracking the property as a dependency for the view. Like@State.
You are defining the data that this view will need to do its job. @State
is only for value types, so you can’t use it on foo which is a reference type.@ObservedObject
allows you to respond to changes like@State
does, but it has one difference.Every time your data triggers an update in a view, SwiftUI recalculates the view’s body, thereby refreshing the view. When this happens, if you have an@ObservedObject
declared in your view, SwiftUI recreates that object when your view refreshes.- Just like with state, when you use the @ObservedObject property wrapper and add that to your view, the framework recognizes that there’s a dependency there. And so in body, when you access that data, we automatically figure out when to update your view. In code, this looks something like this. When you create your view, you add the ObjectBinding property wrapper to a property in your view. And then when you instantiate your view, you just pass the reference to your model that you already have. Note that this creates an explicit dependency in the initializer of the view, which is really great because now anytime I go to instantiate my view, I know that it has a dependency on the model.
And that’s it. And as we do this, each view with the property wrapper will automatically subscribe the changes in our @ObservedObject having @Published property, which means we get automatic dependency tracking. Again, no manual invalidation or synchronization needed.
So I want to pause and take careful note here because if using SwiftUI are value types, any time you’re using a reference type, you should be using the @ObservedObject property wrapper. This way the framework will know when that data changes and can keep your view hierarchy up to date. So that’s how to create a dependency using ObjectBinding on BindableObject. (We will correct this statment later there is one problem ) - In short here you can see we tell ContentView our source of truth or we say this is our dependency and as compare to State framework will subscribe to this property and observe changes it will render UI , Note in State it automatcially managing changes but in ObservedObject we need to tell
- So what we did we first conform
Foo
toObservableObject
which is Combine framework thing, and we say that whenname
value change (since we adde@Published
with name to tell framework observe it’s change) please tell subscriber in this caseObservedObject
underContentView
there is achange and re-render,
As shown in Figure 16 , we pass this ObservedObject
to child reusable view and child view can mutatae this and re-render parent and itself and vice versa.
At first glance, the fact that @State
can be passed down might make you wonder how @ObservedObject
helps you. @State
bindings are internally owned by the view in which they’re created, and therefore, changes in child views can’t be automatically reflected for complex objects. @State
also only tracks the single value type, or binding, and does not support subscribing to, or messaging from, property changes beyond that.@ObservedObject
makes use of Combine to send updates to all subscribers (or bound views). It requires management from the developer, and is preferable for externally referenced objects as it’s much more powerful than @State
As shown in Figure 17 when you tap on Button UI will not re-render , this is beacuse we added @Published
with the name
property of Foo
class and not fullName
so it is not tracking this and not think there is a change in foo
object
Now you can see by adding @Published
to fullname
it is now sending change notification to observer and which muatate observed value and SwiftUI re-render state
@Published Combine
The @Published property wrapper allows you to automatically add a publisher to back your property that will emit a new output value every time you change the original property’s value.
struct Person {
@Published var age: Int = 0
}
The property age behaves just like any normal property. You can get and set its value imperatively as usual. The compiler will, however, generate another property “automatically in your type, with the same accessibility level (private or public), called $age. $age is a publisher that can never error out and its output is of the same type as the age property. Whenever you modify the value of age, $age will emit that new value Note: @Published requires an initial value. You either need to provide a default value for the original property or initialize it when instantiating your type.
Note: ObservableObject
wants Combine Publisher
, currently we are using built in Published
Combine provide we can create our own and get more control / or manual tracking you can say .
As shown in Figure 20, Although using @Published
is the easiest way to control state updates, you can also do it by hand if you need something specific. For example, you might want the view to refresh only if you’re happy with the values you’ve been given. As shown in Fiure 20 if we used @Published
if we change the fullName == “rahim”
it will tell observer there is change but here by using objectWillChange
we get full manual control , we will tell our obsever after adding some addtional condition
All observable objects automatically get access to an
objectWillChange
property, which itself has asend()
method we can call whenever we want observing views to refresh. ObservableObject has a single requirement, an objectWillChange property. ObjectWillChange is a Publisher, and as the name suggests, the semantic requirement of the ObservableObject protocol is that the publisher has to emit before any mutation is applied to the object. By default, you get a publisher that works great out of the box. But if you need to, you can provide a custom publisher. For example, you can use a publisher for a timer, or use a KVO publisher that observes your existing model.
As shown in Figure 21 we create custom publisher using combine framework Each time after 1 second it will re-render UI since it is sending object change notification
Note: If you see the WWDC 2019 Video about DataFlow in Swift UI you will listen the term BindableObject
and @ObjectBinding
. Note BindableObject
is replaced by the ObservableObject
protocol from the Combine framework. (50800624) You can manually conform to ObservableObject
by defining an objectWillChange
publisher that emits before the object changes. However, by default, ObservableObject
automatically synthesizes objectWillChange
and emits before any @Published
properties change. @ObjectBinding
is replaced by @ObservedObject.
In short they deprecated BindableObject
all together!
Binding From Observable Object
Accepting a Binding allow a component to read/write access to a piece of data while preserving a single source of truth . Previously we saw how to derive a Binding
from State
and doing with ObservableObject
is just as easy. You can get a Binding from any property that is a value type on an ObservableObject just by adding a dollar sign prefix in front of your variable and accessing the property
As shown in Figure 22, we are extracted Binding from Observable Object
, Note it must be value type. like fullName
is a String, Now subview can write fullName and re-render every View who has this dependency, @Publihsed to fullName make ContentView dependent on this property and @Binding will add subview to depend on this by derive source of truth. Note @ObservedObject is also a source of truth, If you will not add @Publihsed with fullName it will not work either
As shown in Figure 23, we replace ObservedObject
to StateObject
and it is working as well. Note All the concept we discuss for ObservedObject apply to StateObject
but there are few differences, which we see in next section
StateObject
@StateObject
are avaiable from iOS 14+ as a new additional tool- SwiftUI will keep the object alive for the whole life cycle of the view. Even if it reinitialised by the system (Note I say system still you confused we will see later)
- If init
@ObservableObject
type class in the view struct (Means iniltization happen in view itself not pass externally by some other view), use Property@StateObject
(Like@State
of view type do) - When we annotate the property with
@StateObject
. property will not be instantiated when the view is created. Instead, it will be instantiated just before body runs and kept alive for the whole view life cycle. When the view is not needed anymore, SwiftUI will release the property as expected. You don’t need to fiddle withonDisappear
anymore. Instead, you can use theObservableObject
lifetime to manage your resources. In SwiftUI, views are very cheap. We really encourage you to make them your primary encapsulation mechanism and create small views that are simple to understand and reuse. - This new property wrapper suppose to use if the object is being created inside of view Struct
- SwiftUI’s Views consider that an ObservableObject will always be referenced by some outside class, As SwiftUI’s view are struct. An ObservableObject has possibility to getting deallocate and reinitialized.
ObservedObject
@ObservedObject
are avaiable fromiOS 13+
as a the only tool@ObservedObject
is a property wrapper that you can use to annotate properties of your view that holds a type conforming toObservableObject
. Using it informs SwiftUI to start tracking the property as a dependency for the view.ObservedObject
does not get ownership of the instance you’re providing to it, and it’s your responsibility to manage its life cycle.- If you want to pass dependency to child Model (Class conform to
ObservableObject
) like Binding without initlaization useObservedObject
Lets of theory let’s understand every point using example
As shown in Figure 23, few things to note
- First we created
Counter
class conform toObservableObject
to make UI dependent of reference type model StateView
has child views . First isTextField
which take name as abinding
, Note source of truth of name variable storage is own byStateView
- Second child is
StylishCounterText
which take binding andStylishCounterText
has counter asObservedObject
and Note counter is initialised by theStylishCounterText
usingCounter()
, Note point 3 we defineObservedObject
some behaviour like don’t give ownership to view having property ofObservedObject
or In short don;t initlizedObservedObject
in the view itself StylishCounterText
has another child ViewStylishCounterText1
which is also iniltizing counter variable
Now run the app and tap on StylishCounterText1 Button , few things happen
- First
StateView
,StylishCounterText
andStylishCounterText1
Init called which is fine - Seond when you tap on
StylishCounterText1
it will increment its’ counter so far so good
Now as shown in Figure 26 ,
- WHen you tap on
StylishCounterText
button it will increment the counter which is fine - But wait it again called
StylishCounterText1
Init and reset counter (In short make new Counter() instance so this is the problem, as I said above reinitlized by the system beacuse system re-render subview it reinitlized with @State it managing the life cycle by itself ) - Now read point 2 and 6 of
StateObject
and point 3 of ObservedObject - Note If the parent’s (
StylishCounterText
)state changes, this View(StylishCounterText1
) gets redrawn (pretty normal in a declarative Framework). But also the ViewModel gets recreated and does not hold the State afterward. This is unusual when you compare to other Frameworks (eg: Flutter).
As shown in Figure 27 textFieldOwnBindings
is also own by StylishCounterText1
since it is State it’s property will not be instantiated when the view is created. beacuse it’s life time managmant done by frmaework even View is initlized.
Note : don’t worry every redraw View is initlization framework is doing very efficently and recommend us to use split view as much we can
With just StateObject
keword resolved this problem , you can see StylishCounterText1
is initialised many-time by the framework, (I again say to you it’s very effient don’t worry )
What if we want to use a same counter to StylishCounterText
and StylishCounterText1
, this is the recommended approach
- First know who will initlized counter in our case it’s
StylishCounterText
so marked it’s counter property toStateObject
- Second where to derive this counter property or which child want this proeprty with same state as paretn in our case it’s
StylishCounterText1
so marked counter their withObservedObject
without initlization - In short (State and Binding for value type you see this is the same pattern for reference type
StateObject and ObservedObject
)
Environmental state
There is another tool provided by SwiftUI with the help of Combine which we called @EnvironmentObject.
@EnvironmentObject supports the persistence of objects throughout the lifetime of your app, across multiple views. Whenever you declare an @EnvironmentObject, you’re telling your app to retain a single instance of the object in memory and allow any view you specify to access it. You need to declare it in each desired view for this to work.
For data that should be shared with many views in your app, SwiftUI gives us the @EnvironmentObject
property wrapper. This lets us share model data anywhere it’s needed, while also ensuring that our views automatically stay updated when that data changes.
Think of @EnvironmentObject
as a smarter, simpler way of using @ObservedObject
on lots of views. Rather than creating some data in view A, then passing it to view B, then view C, then view D before finally using it, you can create it in view and put it into the environment so that views B, C, and D will automatically have access to it.
Just like @ObservedObject
, you never assign a value to an @EnvironmentObject
property. Instead, it should be passed in from elsewhere, and ultimately you’re probably going to want to use @StateObject
to create it somewhere.
However, unlike @ObservedObject
we don’t pass our objects into other views by hand. Instead, we use send the data into a modifier called environmentObject()
, which makes the object available in SwiftUI’s environment for that view plus any others inside it. Note: Environment objects must be supplied by an ancestor view — if SwiftUI can’t find an environment object of the correct type you’ll get a crash. This applies for previews too, so be careful.
As shown in Figure Figure 30, we have EnvironmentObjectView paent View which has has counter as @EnvironmentObject
, it also has child view StyleCounterText
which don’t have any state dependency but it has childview StyleCounterText1 which also have child called
StyleCounterText2 which is using EnvironmrntObject, Note this is the power of EnvironmrntObject
it behave like a signelton now we don’t need to pass counter to the root View and leaf view can mutate it’s value and the top most re-render it’s UI
We passed this environment object externally from root class using .environmentObject modifier
As shown in Figure 32 tap on Button StyleCounterText2 or EnvironmentObjectView re-render StyleCounterText2 and EnvironmentObjectView view , and Note Init StyleCounterText is called beacuse it’s parent is re-render so it create from scratch
As shown in Figure 33we only added counter as EnvironmentObject on StyleCounterText2 , we removed the counter dependency on EnvironmentObjectView itself , we still can inilize
We still initialise it when EnvironmentObjectView object is created , Think of it as container
As shown in Figure 35 , we didn’t define environment object so application crashes