Demystify SwiftUI content based onWWDC21:10022 – the Session.

1. Knowledge review

More or less everyone has touched SwiftUI since WWDC19 was released **. Before we talk about Demystify SwiftUI, let’s briefly review SwiftUI:

What is SwiftUI?

“SwiftUI is a declarative UI framework” — Apple

“SwiftUI is an innovative, exceptionally simple way to build user interfaces across all Apple platforms with the power of Swift.” — Apple

When we first recognize ‘SwiftUI’, our first normal reaction is to ask, ‘What is SwiftUI? Apple’s official explanation:

  • SwiftUI is a declarative UI framework. Based on Swift, it takes an innovative and extremely simple approach to building user interfaces that span all Apple platforms.

Just looking at this brief description, we may not be able to visualize it in our minds. We need to supplement this explanation with an example. Declarative is simply described, we want to achieve the interface of the bottom left picture, the description can be like this:

An interface has a vertical layout (VStack), and inside the vertical layout there is a switch (Taggle), and the switch state is bound to isOn (isOn is to record the switch state). Then there is a Text description in the layout. The content of this paper is also associated with isOn, and the status of isOn is used to display on or off.Copy the code

For each sentence described above, we can easily find the corresponding code block in the following code. The code that we write is hierarchical and more descriptive, more realistic, that’s declarative.

SwiftUI is based on Swift. The grammar of Swift is already simple and convenient, but SwiftUI is further encapsulated to make the syntax more concise. And combined with the power of Xcode, building the interface is as easy as building blocks, and the code can be synchronized with the preview interface in real time, very simple and innovative.

Of course, SwiftUI is also amazing for being able to run multiple applications across all Apple platforms (iOS, ipadOS, tvOS, macOS, watchOS) with just one set of code.

Ii. Uncover the mystery of SwiftUI

Here we have a preliminary understanding of SwiftUI. As we know from the above section, SwiftUI is a declarative UI framework. We describe an App through SwiftUI at the top and experience the convenience SwiftUI brings to us. But what SwiftUI does behind the scenes, we don’t really know. So today we take a look behind the scenes at the three core elements of SwiftUI:

  • Identity – How to identify the same or different views over multiple updates to the application
  • Lifetime – SwiftUI tracks the way views and data exist at all times
  • Dependencies – Enable SwiftUI to understand when and why the interface updates

Let’s go through each of these concepts.

Identity

There are two cute puppy pictures in the picture below. How can we tell if they are the same puppy picture or two different puppy pictures?

In fact, we can’t directly tell if they are the same puppy from these two pictures.

So if we label the dog under the picture, and they use the same name, we can probably guess it’s the same dog. More rigorous point of course is to do id card to the dog.

If the dog’s name is not the same, then we can be sure that the dog is not the same dog in the two pictures.

This is the benefit of identity, as is the way SwiftUI identifies views, but there are two types of identity SwiftUI uses:

Explicit Identity

In our example above, assigning a name, or identifier, to a picture of a dog is a form of explicit identity. And the explicit identities that we use in UIKit and AppKit are pointer identities, so here’s the view hierarchy of UIKit or AppKit, UIView and NSView on the graph, they each have a unique pointer to their memory allocation, and that pointer is pointer identity, It’s also a form of explicit identity. We can just use their Pointers to refer to individual views. If two views share the same pointer we’re sure they’re the same view.

But SwiftUI does not use Pointers because the SwiftUI view is of value type. Why use value types? One is that value types are relatively efficient and save performance, and the other is that they make code cleaner and better isolated. For those of you who are interested in this, take a look at WWDC19: SwiftUI Essentials session.

While SwiftUI does not use pointer identities, SwiftUI relies on other forms of explicit identities. For example, the ID parameter of ForEach is a form of explicit identification. We can specify the corresponding view by customizing the ID.

ForEach(., id: \.someProperty) { . }
Copy the code

Let’s look at another example. Here’s a view that uses the ScrollViewReader, with a button at the bottom. Header text binding our custom identifier, click the button, button directly back to the top. As you can see intuitively from the code, we pass this identifier to the scrollTo method of the scrollview agent, telling SwiftUI to scrollTo the specified view if the button is clicked.

Views such as ScrollViewReader, ScrollView, Button, etc., do not require a custom ID. We only need to add ids to views referenced elsewhere.

Of course, just because you don’t need an explicit identity doesn’t mean you don’t have an identity, every view has an identity, even if it’s not explicit. At this point, the concept of Struct Identity is introduced.

Struct Identity

SwiftUI uses a view hierarchy that automatically generates an implicit Identity for a view, known as Struct Identity. Let’s take the example of puppies. Here are two other pictures of puppies. Assuming we can’t know their names, how can we identify them? We can identify them by where they sit, like “the dog on the left” and “the dog on the right.” The way we distinguish them from this relative arrangement is called structural identity.

SwiftUI leverages structural identity throughout the API. For a common example, we use if… else… In conditional statements, we can clearly identify each view. As shown in the code below, the first view is displayed only when the condition is true, and the second view is displayed only when the condition is false. Is it similar to the dog example above? In the previous example, the dog was identified by left and right, but in this case, the view is determined by true and false.

It says if… else… But what SwiftUI sees inside is exactly what it looks like on the right below. The compiler translates the if statement into the _ConditionalContent view. This translation is done through ViewBuilder, a result builder in Swift. The View protocol wraps its body property in a ViewBuilder by default.

The View return type for the body property in the code is a placeholder to indicate that this is a static compound type. Using this generic type, SwiftUI can clearly distinguish between the two views. SwiftUI also works behind the scenes to assign each one an implicit identity.

The official advice here is that if… else… Is the same View, but the parameter conditions are different, we directly use the ternary operator instead. Although you can do either, using the ternary operator allows the two views to maintain the same identity, which also provides a smoother transition and helps maintain the view’s life cycle and state.

// This is not officially recommended
if isGood {
    PawView(tint: .green).frame(maxHeight: .infinity, alignment: .top)
    Spacer()}else {
    Spacer(a)PawView(tint: .red).frame(maxHeight: .infinity, alignment: .bottom)
}
// The official recommendation
PawView(tint: isGood ? .green : .red)
    .frame(maxHeight: .infinity, alignment: isGood ? .top : .bottom)
Copy the code
The Enemy of structural Identity (AnyView)

With structural identity behind us, let’s talk about its NemesisAnyView. Let’s take a look at the following paragraphAnyViewThis function needs to return a single type, so it usesAnyViewTo wrap the different views. This causes the conditional structure of the code to not be visible within SwiftUI, only oneAnyViewThe return type of. becauseAnyViewHides the types of all the views it wraps and makes the code less readable.

We can optimize it as follows. The following code makes the structure for obtaining some Views within SwiftUI much cleaner than the above code. The body attribute is the default (implicit), but for our custom method, we need to add @ViewBuilder, otherwise we will get an error. Of course it’s more intuitive to use switch.

So we’re going to try to avoid AnyView,

  • AnyViewUsing too much often makes the code harder to read and understand;
  • AnyViewStatic type information is hidden from the compiler, causing useful errors and warnings not to be displayed.
  • AnyViewCertain conditions can cause performance degradation. A more appropriate approach is to use generics to preserve static type information.

Let’s take a look at the second element, LifeTime.

Lifetime

Our human life cycle is from birth to death, during which there are various emotional states of sour, sweet, bitter and hot expression. The same is true of views. Once a view is identified, it has a life cycle, and through changes in view values, the view has states throughout its life cycle. The image below shows a bgView with different view values (color) at different points in time, and the corresponding state of the bgView at these points is also different.

It is important to note that the bgView life cycle cannot be defined by a transient view value. The view life cycle must be determined by the view’s identity. When a view’s identity changes or a view is deleted, it is the end of its life cycle. That is, the lifecycle is simply the duration of an identity associated with the view. If the identity is unique, multiple views cannot share one identity. So this also shows that identity stability is critical: unstable identities lead to shorter view life cycles; Stable identities help performance because SwiftUI doesn’t need to create storage for views all the time;

In the view life cycle above, we can see that a view can change its state. For example, in the example we started with, when we flipped the switch and the value changed from True to false, SwiftUI kept a copy of the old view value and performed a comparison before deciding whether to update the view.

struct SwitchView: View {
    @State var isOn: Bool = true
    var body: some View {
        Toggle("Switch is \(isOn ? "On" : "Off")", isOn: $isOn)}}Copy the code

How does the state of a view relate to its life cycle? Associated with view identities via @state and @stateObject, such as isOn, they are the way to persistently store view State. When the view identifies itself, which is when it is first created, SwiftUI allocates storage in memory for @State and @StateObject.

Dependencies

The relationship between dependencies and views

Let’s first examine the view structure of the following code. There are two properties at the top, dog and treat, and those are the dependencies of the view, exceptbodyIs the principal, and all other attributes are dependencies.

As we turn the code into a diagram, we can see more intuitively how the overall view relates to dependencies.

Although the chart structure above is a tree structure, the relationship between views and dependencies is more than that. We add a few dependencies and associate multiple views with them, resulting in a more complex structure than before (lower left). When we rearrange it to avoid overlapping lines, we get the structure on the right, which we call a dependency graph. This structure helps SwiftUI determine which view bodies need to be updated and which do not.

When a dependency changes, a new body value is generated for all views and the body value of the view associated with the dependency is instantiated. Of course, if the dependency change does not meet the view update criteria, the corresponding view is not updated. This is also covered in our Lifetime.

Dependency category

In addition to the normal structure properties, dependencies also include the following property wrappers:

  • @Binding
  • @Environment
  • @State
  • @StateObject
  • @ObservedObject
  • @EnvironmentObject

Properties decorated by these are called dependencies, but only if they are referenced by the view.

Refer to the article

  1. Demystify SwiftUI — Official Apple
  2. “On SwiftUI, this one is enough” – Leung Kai-kin
  3. Cyandev: Declarative UI and Type Systems from SwiftUI
  4. WWDC NOTES: Demystify SwiftUI – Federico Zanetello