How SwiftUI View Works

Sarathi Kannan
7 min readSep 5, 2023

--

We all know SwiftUI is a declarative UI framework which means that we describe what we want for the app at high level and SwiftUI decides exactly how to make it happen. Also it works great in most of the time and that’s why nowadays most of them loved about SwiftUI.

But there will always those moments when SwiftUI does something that we might not expect. To handle those moments, we need to understand a bit more about what SwiftUI is doing behind the scenes to build better code for how to get the results that we’re looked for.

Today’s topic is when SwiftUI looks at your code, what does it see?

The answer is three key aspects on how SwiftUI see our views

  1. Identity: How SwiftUI recognizes views to be same or distinct, across multiple updates of our app.
  2. Lifetime: How SwiftUI tracks the existence of views and data over time
  3. Dependencies: How SwiftUI understands when your interface need to updated and why

View Identity:

In UIKit, views are reference types and it has pointers which identify their views. In SwiftUI, views are value types and they don’t have pointers. In order to be efficient and optimized, SwiftUI needs to understand if the views are same or distinct. It is critical aspect of how SwiftUI understand the app.

Views that share same identity represent different state of the same conceptual UI element.

Let’s take one example of two views from the different screen states.

  1. Are those views are same view in a different state?
  2. Are those views are completely distinct?

If they’re the same view, SwiftUI will apply the new state of the view, for example moving the view from location to another location

If they’re distinct, that means the changes of state will make them transition independently, such as fading in and out.

Connection views across the different state is so important, because that’s how SwiftUI understands how to transition between them.

There are two ways of identifying the views in SwiftUI

  1. Explicit Identity: defined by using custom or data-driven identifiers
  2. Structural Identity: defined by view type and position in the view hierarchy.

Explicit Identity:

Views can be identified using custom or data driven identifiers. It can be provided by using identifier id(…). It binds view’s identity to the given value which needs to be hashable.

Let’s take one example

Fig 1: Compile error on ForEach

Let’s say we have list of users, each user has a unique name and an email. To show scrollable list of users, ForEach struct can be used.

However, this will not compile and there will be an error: Referencing initializer ‘init(_:content:)’ on ‘ForEach’ requires that ‘User’ conform to ‘identifiable’.

This problem can be fixed by either implementing the identifiable protocol into ‘User’ struct or providing the key path. Either way it will let the SwiftUI know which explicit identity the UserView should have.

Fig 2: Solve error by providing Explicit Identity

This new code will compile and UserView will be identified by the email, since the email of the user is designed to be unique.

Another use case where explicit identity is regularly used is an ability to do a manual scrolling to one of the sections of the scroll view.

Fig 3: Explicit Identity in id() modifier

In this example, tapping on button will scroll the view to header text. The .id() modifier is used to provide custom identifiers to our views, giving them the explicit identity.

Structural Identity

Every SwiftUI view must have an identity. If the view doesn’t have an explicit identity, it has a structural identity. A structural is when the view is identified by using its type and its position in a view hierarchy. SwiftUI uses the view hierarchy to generate the implicit identity of the views.

Let’s take a example which change the view based on condition

Fig 4: Structural Identity for distinct view

In this example, it created two completely distinct views depending on the changeView boolean state. Actually SwiftUI will create an instance of ConditionalContent view behind the scenes _ConditionalContent<Text, Text> . This ConditionalContent view is responsible for one or another view based on the condition. And these Text views have different view identities, because of condition used. In this case SwiftUI redraw the view once toggle has changed. This is important to understand that’s not the same Text view, these are the different Text views and they have their own structural identities.

Let’s rewrite the code in a way that Text View identity preserved.

Fig 5: Preserving Strutural Identity by removing condition

In the re-written code will keep the structural identity same, and whenever changeView toggles, only the displaying content change but view won’t get created.

In this case structural identity of the view doesn’t change. Apple recommends preserving the view’s identity by putting conditionals with in the view modifier as opposed to use if/else statements.

Structural Identity and its understanding is the key for a better optimized app with fewer bugs.

There are few things to keep in mind to achieve better performance

  1. Use stable identifiers for views if they are explicitly provided.
  2. Avoid AnyView if possible — AnyView makes it hard for SwiftUI to optimize and identify each view, avoid as much as possible use @ViewBuilder if needed.
  3. Maintain view’s identity, if you can, don’t use conditional statements to preserve view identity.

View Lifetime

In this topic, we explores how identity ties into the lifetime of your views and data. It helps you to understand how SwiftUI works.

Identity allows us to define a stable element for different values over time. During its lifetime, a view can change its state, view identity connects these different state values as a single entity.

For Ex:

struct TestViewLifetime: View {
@State private var changeView = false
var body: some View {
VStack {
Toggle("Change View", isOn: $changeView.animation())
.fixedSize()
Text(changeView ? "View A" : "View B")
}
}
}

When we toggles the changeView boolean from false to true and true to false, SwiftUI will keep around a copy of the old view value to perform a comparison and know if the view has changed. After that, the old value is destroyed.

What it is important to understand here is that the view value is different from the view identity. View values are ephemeral and you should not rely on their lifetime. But what you can control is their identity.

When a view is first created and it appears, SwiftUI assigns it an identity using a combination of the techniques discussed above.

Over time, driven by updates, new values for the view are created. But from SwiftUI’s perspective, these represent the same view. Once the identity of the view changes or the view is removed, its lifetime ends. Whenever we talk about the lifetime of a view, we are referring to the duration of the identity associated with that view. Being able to connect the identity of a view with its lifetime is fundamental to understand how SwiftUI persists your state.

So let’s bring State and StateObject into the picture.

When SwiftUI is looking at your view and sees a State or a StateObject, it knows that it needs to persist that piece of data throughout the view’s lifetime. In other words, State and StateObject are the persistent storage associated with your view’s identity.

At the beginning of a view’s identity, when it’s created for the first time, SwiftUI is going to allocate storage in memory for State and StateObject using their initial values. Throughout the lifetime of the view, SwiftUI will persist this storage as it gets mutated and the view’s body is re-evaluated.

View Dependencies:

View dependencies is used to define how SwiftUI updates the UI. All view properties are dependencies of a view.

struct TestView: View {
@Binding var isOn = false // this is a dependency
var name: String // this is a dependency

var body: some View {
...
}
}

A dependency is just an input to the view. Each view has its own dependencies, multiple views can depend on the same dependency source:

  • SwiftUI maintains a dependency graph of all these dependencies.
  • when any dependency changes, SwiftUI will call each view’s body, producing a new body value, for all views depending on that dependency

Conclusion:

From the above, we can clearly see how the SwiftUI works when it saw our code and see how to improve the performance of the app by preserving view identity, view lifetime and view dependencies. These concepts are essential to write better optimized, smooth, and effective iOS applications with SwiftUI.

I hope this article helps you to understand about how SwiftUI works behind the scene.

However if you have any questions, leave me comment below, I will answer for your questions.

If you like this article, please follow me and provide clap.

Thank you ! Happy Coding !

Reference: Demistify SwiftUI

Follow me in Linked In | Sarathi Kannan

--

--

Sarathi Kannan

Passionate Mobile Application Developer, iOS, Swift, SwiftUI, Objective C, Flutter