SwiftUI Part 2 (DataFlow in SwiftUI — 1 )

Ali Akhtar
24 min readJun 18, 2021

--

https://developer.apple.com/documentation/swiftui/state-and-data-flow

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.

Figure 1

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.

Figure 2

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 @Statethe 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.

Gif 1

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 @Stateis 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’

Figure 3

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)

Gif 2

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

Figure 4

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 @Statebinding 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

Figure 5

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

Gif 3

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)

Figure 6

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

Figure 7

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 like String, Int, Bool, structand enum. If you want the view to own a reference type, such as a class, you use @ObservedObject or @EnvironmentObject or @StateObjectinstead. You’ll learn about these later in this part.

Figure 8

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

Figure 9

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

Figure 10

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.

Figure 11

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 . @Statebinding 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 @Bindingattribute 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 the StylishText.

Figure 12

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,

Figure 13

As shown in Gif 1

  1. When we launch app it render ContentView and it subviews
  2. 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 )
  3. Third we agains change value from empty to empty framework know no change so it will not render UI at all
Gif 1

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.

Figure 14

As shown in Figure 15 few things to understand , Some concept will be from new Combine framework

  1. The ObservableObject conformance allows instances of this class 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.
  2. @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.
  3. @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 short ObservedObject is a property wrapper that you can use to annotate reference type properties of your view that holds a type conforming to ObservableObject. 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.
  4. @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.
  5. 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 )
  6. 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
  7. So what we did we first conform Foo to ObservableObject which is Combine framework thing, and we say that when name value change (since we adde @Published with name to tell framework observe it’s change) please tell subscriber in this case ObservedObject under ContentView there is achange and re-render,
Figure 15

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

Figure 16

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

Figure 17

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

Figure 18

@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 .

Figure 19

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 a send() 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.

Figure 20

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

Figure 21

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

Figure 22

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

Figure 23

StateObject

  1. @StateObject are avaiable from iOS 14+ as a new additional tool
  2. 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)
  3. 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)
  4. 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 with onDisappear anymore. Instead, you can use the ObservableObject 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.
  5. This new property wrapper suppose to use if the object is being created inside of view Struct
  6. 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

  1. @ObservedObject are avaiable from iOS 13+ as a the only tool
  2. @ObservedObject is a property wrapper that you can use to annotate properties of your view that holds a type conforming to ObservableObject. Using it informs SwiftUI to start tracking the property as a dependency for the view.
  3. ObservedObject does not get ownership of the instance you’re providing to it, and it’s your responsibility to manage its life cycle.
  4. If you want to pass dependency to child Model (Class conform to ObservableObject) like Binding without initlaization use ObservedObject

Lets of theory let’s understand every point using example

As shown in Figure 23, few things to note

  1. First we created Counter class conform to ObservableObject to make UI dependent of reference type model
  2. StateView has child views . First is TextField which take name as a binding , Note source of truth of name variable storage is own by StateView
  3. Second child is StylishCounterText which take binding and StylishCounterText has counter as ObservedObject and Note counter is initialised by the StylishCounterText using Counter(), Note point 3 we define ObservedObject some behaviour like don’t give ownership to view having property of ObservedObject or In short don;t initlized ObservedObject in the view itself
  4. StylishCounterText has another child View StylishCounterText1 which is also iniltizing counter variable
Figure 24

Now run the app and tap on StylishCounterText1 Button , few things happen

  1. First StateView , StylishCounterText and StylishCounterText1 Init called which is fine
  2. Seond when you tap on StylishCounterText1 it will increment its’ counter so far so good
Figure 25

Now as shown in Figure 26 ,

  1. WHen you tap on StylishCounterText button it will increment the counter which is fine
  2. 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 )
  3. Now read point 2 and 6 of StateObject and point 3 of ObservedObject
  4. 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).
Figure 26

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

Figure 27

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 )

Figure 28

What if we want to use a same counter to StylishCounterText and StylishCounterText1, this is the recommended approach

  1. First know who will initlized counter in our case it’s StylishCounterText so marked it’s counter property to StateObject
  2. 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 with ObservedObject without initlization
  3. In short (State and Binding for value type you see this is the same pattern for reference typeStateObject and ObservedObject)
Figure 29

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

Figure 30

We passed this environment object externally from root class using .environmentObject modifier

Figure 31

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

Figure 32

As shown in Figure 33we only added counter as EnvironmentObject on StyleCounterText2 , we removed the counter dependency on EnvironmentObjectView itself , we still can inilize

Figure 33

We still initialise it when EnvironmentObjectView object is created , Think of it as container

Figure 34

As shown in Figure 35 , we didn’t define environment object so application crashes

Figure 35

Useful Links

--

--