# Behind the scenes of UI: Part 2 - SwiftUI

if you prefer to watch video you can find it [here](https://youtu.be/ue6tBz03cVQ).

You can say that UIKit is great, but SwiftUI is a future. But don’t hurry up to burry UIKit, as you will understand soon, it is still very useful to understand how SwiftUI works under the hood.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708317609788/8b17673d-4739-4f93-a720-5900bdfce6b7.png align="center")

SwiftUI is a declarative data-driven UI framework.

* **Declarative** means that you describe the desired UI and its behavior by providing the view’s body, and SwiftUI handles the internal implementation details.
    
* **Data-driven (or state-driven)** means that the UI is closely tied to the underlying data or state, and any changes in the state will be automatically reflected in the UI.
    

SwiftUI conceals its entire render loop process behind the **body** of the view provided to us for constructing the View.

```swift
struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
	}
}
```

Structure of the body defines both **layout** and **content** of the view. Let’s try to figure out what stands behind the body.

> **DISCLAIMER**: Things we will discuss next are not documented and we have no access to the actual SwiftUI code/implementation. So I will try to give a guess how it can possibly work to help you build your own mental model about it.

### **View and Render tree**

Similarly to UIKit, SwiftUI has its **view tree**. However, in contrast to UIKit, where UIView is an **object**, views in SwiftUI are **structs**, making the view tree **ephemeral**. This implies that view values in SwiftUI are **transient** and can be recreated whenever the **state** changes. Direct manipulation of the view tree isn't possible in SwiftUI. Instead of mutating the view tree directly as in UIKit, we modify the state, prompting SwiftUI to calculate a new view graph from this state. Views are recreated upon state changes, and the body is re-evaluated. SwiftUI optimizes this process by avoiding re-execution of a subview's body when it remains unchanged. To determine when to re-evaluate the body, SwiftUI utilizes the **Attribute Graph**. This is an internal component that builds a dependency graph for your data and its associated view tree. Unlike the view tree, the Attribute Graph is **persistent** and remains even when the body re-executes. And it has to, because it stores the state that must remain after view tree recreation. With each new generation of the view tree, SwiftUI performs a diff against the current state of the render tree, efficiently applying the necessary changes by adding and removing nodes from the render tree.

Chris Eidhof [refers](https://chris.eidhof.nl/presentations/day-in-the-life/) to the **Attribute Graph** as the **Render Tree** to simplify the understanding of the concept. This makes sense to me because it serves as a sort of source of truth for rendering. Additionally, it's a fitting analogy with the render tree in UIKit.

### View Render Loop

We can split render loop into several phases:

* Evaluation Phase
    
* Layout Phase
    
* Rendering Phase
    

### **Evaluation Phase**

Let’s highlight again: the view tree is **ephemeral**, while the render tree is **persistent**. Initially, the root node of the render tree should be created first, and it is probably something like node for ContentView to start with. Next, the view tree should be constructed. To initiate this process, ContentView's body should be evaluated. Step by step, from superviews to subviews in the hierarchy, SwiftUI prepares the view tree, composing it into a complex generic structure hidden beneath `some View`. View tree is used to construct the render tree. Once render tree construction is complete, SwiftUI can discard and throw away the entire view tree.

When the state is changed for a render tree node, the affected render sub-tree is invalidated. SwiftUI then rebuilds the corresponding view subtree and re-evaluates the bodies if necessary to update the render tree structure. As a result of this phase, SwiftUI should have the render tree rebuilt and ready for the subsequent phases.

### **Layout phase**

After Attributed Graph is (re)constructed and ready, SwiftUI performs layout calculations to determine the position, size, and arrangement of each view within the parent view or container.

Layout phase is very well explained in articles from [Alex Grebenyuk](https://kean.blog/post/swiftui-layout-system) and [Paul Hudson](https://www.hackingwithswift.com/books/ios-swiftui/how-layout-works-in-swiftui):

Layouts proceed top down: parent views offer their available space to their children, which decide their size based on that.

There are three steps in SwiftUI layout process:

**1\. Parent Proposes Size for Child**

First, the root view offers the text a proposed size – in this case, the entire safe area of the screen, represented by an orange rectangle.

**2\. Child Chooses its Size**

Text only requires that much size to draw its content. The parent has to respect the child's choice. It doesn't stretch or compress the child.

**3\. Parent Places Child in Parent’s Coordinate Space**

And now the root view has to put the child somewhere, so it puts in right in the middle. That means views set the position of child’s, but not by SwiftUI externally.

> **NOTE**: Layout in SwiftUI is easy until it's not.

Steps sound pretty easy but what sometimes makes layouts in SwiftUI complex (especially after years of UIKit development) is that each view (or view modifier) behaves differently when it comes to determining the actual size from the proposed size. For example, a shape always fits itself into the proposed size; a horizontal stack takes on the size that’s needed for its children (up to the proposed size); and a text view takes on the size needed to render its text, unless its size exceeds the proposed size, in which case the text gets clipped. You need to keep all of that in mind.

### Rendering phase

Once layout is completed views are supposed to be rendered on the screen. The exact process SwiftUI uses for rendering remains uncertain. Yet, it's reasonable to assume that SwiftUI relies on underlying UIKit, Core Animation, Core Graphics and Metal to do that. And keeping that in mind there should be some kind of mapping from SwiftUI view/render tree into UIKit view tree. In this case **Rendering phase** is probably can be better called **Mapping to UIKit phase**. We'll try to confirm this assumption in the next part of this post.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708317905290/f0188dd0-ecb8-4bdd-b158-eb7514b2efcb.png align="center")

### Behind the render loop

That was a bunch of theory in the previous post. How about exploring some real details we can uncover from the debugger?

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708317930498/d76d9208-20b3-4c22-823b-39da4e4b529d.png align="center")

Let’s create a new SwiftUI project in the Xcode and try to see what happens before body is called by placing a breakpoint inside the view body.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318107482/cfaef476-bd85-4d30-8778-1fe612eee5ac.png align="center")

We can make a few observations here:

* `AG::Graph` represents the Attribute Graph we previously mentioned. Occasionally, there might be a lengthy stack trace related to AG.
    
* `__lldb_unnamed_symbol123456` denotes parts of the SwiftUI code, which remain hidden from us.
    

However, our primary interest lies in the lower part of the stack trace. There, we might observe something very interesting:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318121600/680d7ba1-ac02-4f47-995e-1d1e061c3142.png align="center")

The bottom of the stack should actually provide you with a strong hint of how SwiftUI works internally:

* `UIApplicationMain`
    
    * `→ __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__`
        
        * `→ CATransaction.commit()`
            
            * `→ UIView(CALayerDelegate).layoutSublayersOfLayers`
                

Looks pretty familiar, doesn't it? It's just like the UIKit render loop we discussed in the previous post. This occurs in a pure SwiftUI application without a single UIView added by us. It actually proves that SwiftUI incorporates UIKit and CoreAnimation under the hood, inheriting plain old UIKit’s render loop we previously discussed.

During each runloop iteration, when the CALayer/UIView hierarchy traversal takes place as a part of the implicit CATransaction’s layout phase, SwiftUI conducts its render loop starting from the `layoutSublayers(of:)` call of the UIView that hosts the SwiftUI view hierarchy (to be precise, `_UIHostingView`).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318190498/af510534-d8f4-47ad-bbb7-d8629c916b27.png align="center")

Now let’s try to understand connection of SwiftUI and CATransaction better.

### **Connection with CATransaction**

The same way as UIKit, in SwiftUI views will be rendered on screen when the implicit `CATransaction` is committed. You can observe connection of SwiftUI and CATransaction with simple example. We can add `CATransaction.begin()` into `onTapGesture`.

> **DISCLAIMER**: I am strongly recommend you not to use CATransaction with SwiftUI in your codebase. It is just a demonstration example just for fun.

```swift
struct SwiftUIView: View {
    
    @State var color: UIColor = .red
    
    var body: some View {
        Circle()
            .fill(Color(color))
            .onTapGesture {
                color = .yellow
                CATransaction.begin()
				color = .green
            }
    }
}
```

As you probably remember from the post about UIKit, when you call `CATransaction.begin()`, a new transaction object is created and pushed into the shared transaction stack. So if you don’t do `CATransaction.commit()` after that, you simply break the transaction stack because of the way how transactions work. No layer tree changes will be committed from your app to the render server after that. So in our example we will not see nether yellow or green color update on the screen after tap. That shows us the connection between SwiftUI and CATransaction. SwiftUI relies on transactions the same way as UIKit does.

If we try to build similar example with explicit transaction as we did for UIKit we will discover another important aspect of SwiftUI.

```swift
struct SwiftUIView: View {
    
    @State var color: UIColor = .red
    
    var body: some View {
        Circle()
            .fill(Color(color))
            .onTapGesture {
				CATransaction.begin()
                color = .yellow
				CATransaction.commit()
                sleep(3) // blocks the main thread for 3 seconds
				color = .green
            }
    }
}
```

In similar UIKit example we would see yellow color in the middle once explicit transaction is commited before main thread had beed blocked by sleep().

```swift
@objc func buttonTapped() {
    CATransaction.begin()
	button.backgroundColor = .yellow
	CATransaction.commit()
    sleep(3) // blocks the main thread for 3 seconds
    button.backgroundColor = .green
}
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318319968/7aa4994a-1587-4829-a255-75e6f0498892.png align="center")

But in SwiftUI, it seems to not working the same way, but why? 🤔

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318330147/98522fd4-3dc7-415c-8fb4-13a79d94f1f2.png align="center")

Let’s remember how CATransaction works. It commits the new changes of layer tree to the render server. But in our SwiftUI example we didn’t do any changes to any CALayer inside our explicit transaction (between begin and commit).

```swift
CATransaction.begin()
color = .yellow // changes only color state property, no CALayer changed
CATransaction.commit()
```

We only changed the state of SwiftUIView. And it needs to do another SwiftUI render loop to actually rebuild view tree and render tree to apply changes to underlying layers.

This is actually why none of the changes after the body evaluation or onAppear call aren’t rendered instantly. At the moment of body evaluation/onAppear/onChange underneath CALayer properties are not yet changed so at that moment nothing to commit yet. We need to do a full SwiftUI render loop first to change underlying layers that will be committed with implicit transaction and eventually rendered.

We can extend this example a bit. We already know that in the root of Swift hierarchy lies `_UIHostingView`. It is UIKit UIView and it has it’s CALayer under the hood. There is no way to access `_UIHostingView` from SwiftUI code but If we set `_UIHostingView` to a global variable from UIKit side, we will be able to access it inside `onTapGesture`. (Never do anything like that in your production code 🙈)

If we change the `backgroundColor` of hostingView inside the explicit transaction on tap gesture:

```swift
var hostingView: UIView? // Set it globally

struct SwiftUIView: View {
    
    @State var color: UIColor = .red
    
    var body: some View {
        Circle()
            .fill(Color(color))
            .onTapGesture {
				CATransaction.begin()
				hostingView?.backgroundColor = .yellow
                color = .yellow
				CATransaction.commit()
                sleep(3) // blocks the main thread for 3 seconds
				color = .green
            }
    }
}
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318420433/c33ed9a8-77b9-46fc-8509-22b1079a8e53.png align="center")

Flow will be like that:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318430377/19156198-2059-4ad0-91c7-fe349ab3e51d.png align="center")

It shows us that CATransaction works with layer tree changes as expected even inside SwiftUI code. But it also shows that it makes no sense to use explicit CATransaction in SwiftUI views once you usually don’t access CoreAnimation layer tree directly from SwiftUI (and you should not).

We used hosting view in the last example, but didn’t talk about it much so far. Let’s discuss it next.

### HostingView as a host

If we look in Xcode structure while debugging a new UIKit project we will see:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318472336/2ab83e42-9cd8-49e4-a84e-c4a0cf68e593.png align="center")

And in the root of a new SwiftUI application you will see exactly the same structure but with `HostingViewController` with `_UIHostingView` that hosts SwiftUI `ContentView`:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318479953/fc65436e-bdae-4f73-906a-06d920a5ee3c.png align="center")

All of SwiftUI's view tree is hosted by `_UIHostingView`, controlled in turn by the `UIHostingController`. Any changes to the view structure applied to `_UIHostingView` and its subviews by SwiftUI internally are rendered by the usual CoreAnimation render loop we discussed earlier.

When you add new SwiftUI views to your ContentView body, SwiftUI will add UIKit subviews to `_UIHostingView` if **needed**. I mentioned "**if needed**" because not every SwiftUI view corresponds directly to a UIKit UIView under the hood. There are multiple ways graphics can be rendered in SwiftUI. SwiftUI can internally create a `UIView` to display them, or it might not. For instance, while some views like `VStack` participate in layout, they don't have their own `UIView` under the hood.

Let’s consider simple SwiftUI view as example:

```swift
struct ContentView: View {	
		var body: some View {
			VStack {
			    Image(uiImage: .strokedCheckmark)
	            Text("Text")
	            TextField("Title", text: .constant("TextField"))
			}
	  }
}
```

View **body** generic type hidden behind `some View` will be following:

```swift
VStack<
	TupleView<
		(Image, Text, TextField<Text>)
	> 
>
```

Looking at the above code sample, it’s easy to jump to the conclusion that the resulting UI will consist of:

* Vertically aligned `UIStackView` which contains:
    
    * `UIImage`
        
    * `UILabel`
        
    * `UITextField`
        

However, only part of those assumptions is actually true. Unlike CALayer within each UIView, not every SwiftUI view has a UIView behind it. Some SwiftUI views do utilize UIKit under the hood, although not all of them.

In our example we will see the following structure of subviews added to hosting view:

* `_UIGraphicsView` for `Image`
    
* `CGDrawingView` for `Text`
    
* `ViewHost` (It’s actually just `UIViewRepresentable` ) with hosted `UITextField` for `TextField`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318788018/3dea615f-e113-4b0b-a841-82f8ee9c7eaf.png align="center")

I'll attempt to group all views based on the way they are backed up by UIKit:

* No UIView behind
    
    * Layout/structure based views - `Spacer`, `V(H/Z)Stack`, `LazyH(V)Stack`
        
* Backed by `_UIGraphicsView`
    
    * Graphics based views - `Color`, `Image`, `Divider`
        
* Backed by `CGDrawingView` class (subclass of `_UIGraphicsView`)
    
    * Text based views - `Text`, `Button`, `TextEditor`
        
    * `CG` prefix gives us a hint that SwiftUI uses CoreGraphics for text rendering the same way as UIKit does it.
        
* Specific UIKit.UIView hosted by UIViewRepresentable
    
    * `TextField` (with UITextField), `ProgressView`, `Slider`, `List`
        
* Composition of views listed above
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318823413/d8dedbf9-20cf-498b-b0e8-933d6204f1cf.png align="center")

> **IMPORTANT NOTE**: You need to keep in mind that underlying implementation of SwiftUI views can change with time. I guess it is the reason why Apple keeps SwiftUI implementation private, they can change it with time but SwiftUI API will remain he same. For now, SwiftUI mostly wraps existing UI frameworks like UIKit, CoreAnimation, Core Graphics. In the future if Apple decides to, they can deprecate UIKit without changing the SwiftUI APIs and rewrite the foundation of it. I see that as a very long way though, so UIKit probably will be there for some time.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708318870510/7921f722-ef1c-4aff-893b-ac4a0ee5a63c.png align="center")

### Recap

* SwiftUI is a **declarative** and **data-driven** UI framework.
    
* **The Role of the**`body`:
    
    * The `body` of a view defines both its layout and content.
        
    * Behind the `body`, SwiftUI operates through a complex system involving **view tree** and **attribute graph** also known as **render tree**.
        
* **View Tree and Render Tree:**
    
    * SwiftUI has a view tree, but unlike UIKit, views in SwiftUI are transient structs.
        
    * Views are recreated when state changes, and SwiftUI optimizes this process by leveraging the attribute graph to track state changes.
        
* **View Render Loop:**
    
    * The view render loop involves three phases:
        
        * evaluation phase
            
        * layout phase
            
        * rendering phase (or mapping to UIKit phase)
            
    * **Evaluation phase** includes **rebuilding view tree, diffing** and **body evaluation**.
        
    * **Layout phase** proceed goes top down: parent views offer their available space to their children, which decide their size based on that.
        
    * We don’t know much about SwiftUI **rendering phase**. But we know it relies on underlying UIKit, Core Animation and Core Graphics for that. **Rendering phase** is probably can be better called **Mapping to UIKit phase**.
        
* **CATransaction and SwiftUI:**
    
    * By relying on UIKit/CoreAnimation SwiftUI internally utilizes implicit `CATransaction` for managing changes to the layer tree. Although you don't use `CATransaction`s directly in `SwiftUI` apps, the framework internally still uses Core Animation and `CATransaction`s for drawing and animations. Together with the render server, Core Animation is very foundational to iOS and SwiftUI.
        
* **HostingView and UIKit Integration:**
    
    * SwiftUI view tree is hosted by `_UIHostingView`, controlled by `UIHostingController`.
        
    * Some views internally use UIKit views (like i.e `UITextField` used and hosted by `TextField`). Even though not all SwiftUI views are mapped to UIKit views internally.
        
    * SwiftUI currently wraps existing UI frameworks like UIKit, CoreAnimation, and Core Graphics. SwiftUI abstracts away complexities and could potentially allow Apple to evolve underlying implementations in the future without changing the SwiftUI API.
        

Thank you for reading and see you in the next posts!
