Power of SwiftUI

For years there have been open debates in the iOS community on whether to manage views programmatically or through xib files. Both have their own pros and cons. Swift UI brings much needed closure to that. 

The XML based xib files (and Storyboards) let us design our views visually in Xcode’s interface builder. It’s a powerful and interactive tool that lets you design your UI to the minutest detail and provide connections between the visual elements and code. At the same time, it can be a nightmare to resolve conflicts by going over an obscure XML syntax. Also, even if you use xib, you still have to handle a lot of functionality programmatically.

Some prefer to create the entire UI programmatically – laying out the frames, adding autolayout constraints, setting color, font, and other elements of style. This gives us complete control over the UI and it’s easier to manage conflicts. But it is quite a hassle to blindly run your code over and over again to check the result. Managing so many visual elements in your code can lead to very verbose and crowded code which is prone to errors. There is tons of boilerplate code involved too.

This is where SwiftUI comes in. SwiftUI uses a declarative syntax i.e.  a non-imperative style of programming in which programs describe their desired results without explicitly listing commands or steps that must be performed. You can simply state WHAT your user interface should do, without taking pains over HOW it should do it. For example, creating a view with a list of items containing text and an image is as easy as this:

import SwiftUI

struct AlbumDetail: View {
         var album: Album
         var body: some View {
                 List(album.songs) { song in 
                         VStack(alignment: .leading) {

The SwiftUI View

There are a few things of notice in the snippet above, apart from the ridiculously minimal code:

  1. The SwiftUI View is a struct
  2. SwiftUI views conform to the View protocol
  3. Move over ‘Any’, there’s a new keyword called ‘some’ (does anyone remember ‘determiners’ from Grammar?)!

It is common for even the most seasoned developer to be daunted by these alien concepts that have ruffled the harmony of the old faithful UIKit. But if the rapid updates to SwiftUI in just a couple of years are anything to go by, SwiftUI is here to stay. Some may even go so far as to say that it is the future. In any case, the concepts of SwiftUI are anything but complex. Simplicity and ease lie at its heart. There’s a beautiful Swift-y logic operating here, which is hard to ignore once you get the hang of it. So let’s dive into these concepts one by one.

‘struct’ vs ‘class’

All views in Swift UI are structures, which give us some advantages. 

First, performance is improved because initialising and managing structures, which are simpler, is quicker compared to Classes. A struct only holds the information we use to create it, while a UIView subclass has numerous properties that are inherited and stored in memory, even though we may not use them.

Second is mutability. When we create a UIView, we get a reference to the object and its properties can be changed from virtually anywhere in the code, at various points of a view’s lifecycle – often leading to buggy code that is hard to debug. SwiftUI structs have the advantage of being calculated only at a single point, the ‘body’ var in the sample above, for example. There is no reference to be mutated at other places, therefore, establishing a clean and single point of updates. 

The View Protocol

All Swift UI Views must conform to the View Protocol, and for conforming we need to have one parameter called body which returns some View. We’ll come to the ‘some’ keyword later.

var body : some View {}

Basically we need to return a View which needs to be computed and rendered. 

This raises the question: when will var body: some View { } get called?

Swift UI can ask a new body whenever it is required and we don’t know when it gets called or how many times it gets called, so it’s important to never put any long processing tasks inside the body variable.

Some examples when the body variable may get called:

  • Whenever size of the container changes
  • Whenever user rotates the phone
  • Whenever associated state/observable variables change, which brings us to a crucial component of SwiftUI: binding with data through state variables.

State Variables

SwiftUI is fundamentally different from the way UIKit works in one more regard – since the views are structures, they are not stored/passed around and are recreated every time the UI is re-rendered. What happens to the variables we may define inside the struct? The answer is simple, they too are re-created and initialized every time. How, then, can we bend SwiftUI to our will? How can we update the text on screen, for example, if we cannot store anything inside these transient structures?! 

SwiftUI achieves this by depending on what are called State Variables. They are marked by the @State keyword and can be defined inside the ‘body’ struct. Basically, state variables will retain their state even after their containing Swift UI views get reallocated. For example, this is how we add a state variable which we want to persist across renderings of the view:

@State var isOn: Bool

But how do these variables persist?

State variables will be allocated and maintained in a different part of memory so allocation/deallocation won’t affect them. Lifetime of these variables is the lifetime of the view on screen. 

But that’s not all. SwiftUI takes binding quite seriously and literally. It means: whenever there is change in these variables then immediately Swift UI will ask for the new ‘body’ and then render it.

Apple mentioned this as keeping a Single Source of truth in WWDC, a way of forcing model and View to always be in sync. This does away with multiple points of mutating the view’s properties, as was the case with UIView. 

Are we done with SwiftUI’s novelties yet? Not quite! State variables are for simple objects like String, Bool, Int, etc. For more complex data, we have: 

ObservedObject Variables

ObservedObject is for the complex objects which conform to ObservableObject protocol. Whenever our object changes, Swift UI listens to the change and updates the view by generating a new body.

There’s a catch though: ObservedObject variables are not like state variables in terms of lifetime: their lifetime is the same as SwiftUI View’s lifetime, so whenever the struct is reallocated the ObservedObject will also get deallocated and created anew. But that defeats the purpose, doesn’t it? What if we want variables that are not bound to the View’s lifetime? 

Luckily, there’s another flavour of these variables: the StateObject.

Simply put, StateObject = State + ObservedObject

That is, StateObject variables:

  • can be observed like State variables. Changes in the variables will lead to updates in the View
  • are not re-allocated when View is redrawn, they are allocated and maintained in a different part of memory.
  • support complex data types like ObservedObject

Let us move on to the question that still remains unanswered.

How and when is Swift UI getting rendered?

As mentioned above, SwiftUI’s View can be deallocated and reallocated whenever its parent body changes.

So does this mean that whenever we change something as small as a boolean state, the whole screen will be destroyed and re-rendered? What a massive waste of precious computing resources, we’d say, and that too with all this talk of performance and structs! Is SwiftUI, then, a fancy but un-optimised paradigm? 

The answer is a vehement ‘No!’

As mentioned earlier, SwiftUI’s View is just a description of how our screen needs to look, it doesn’t have any actual View code or directions for rendering.

Take a look at the diagram above. Whenever a SwiftUI View is allocated( or reallocated), a new ‘View Graph’ of sorts gets created. The View Graph will have a complete list of views which are going to be drawn on the screen with their properties like color, opacity and frame. So whenever a new graph is created, it will be compared with the older one and update only the part of it which needs to be updated. A diff-able graph, if you will!

For example

  var body: some View {
	  HStack {
             Text(isOn ? "Off" : "On")

Whenever isOn changes, a new graph will be created and if we compare with the old graph then only the isOn string must change, so Swift UI will just update that text alone on the existing layer instead of creating a whole new Text layer. 

Neat and optimised, isn’t it?

In conclusion

There’s one thing we all can agree on: SwiftUI is here to stay. No matter the flaws and the teething troubles that are inevitable in the initial stages. After years of getting cosy with UIKit, it is totally fair that developers will adopt SwiftUI with scepticism. Still, it is a promising new addendum to the iOS universe. It has the potential to completely change the way UI is implemented and how it is bound with changes in data. It is better to hop on early onto the platform, because there are going to be massive updates as SwiftUI matures and finds its feet. We have barely scratched the surface of its potential in this article. In our next article, we will have a look at some common Views in SwiftUI and how they are related with their UIKit counterparts.

Leave a Reply