01. Basic Concepts of SwiftUI (1)

It will take about 5 minutes to finish reading this article.

Let’s start with a piece of SwiftUI code and its Canvas display as follows:

Explain this code:

1
2
3
4
5
6
7
8
9
10
11
import SwiftUI

struct ContentView: View {
var body: some View {
Button("Hello, world!") {
}
.background(.red)
.frame(width: 200, height: 200)
}
}

(1) Create a ContentView structure and follow the View protocol. This means that ContentView is a view type and needs to implement the body property.

(2) The body attribute returns a some View type, which means that any view that conforms to the View protocol can be returned.

(3) In the body, we have created a view that displays the text “Hello World!” using the Button view.

(4) Several modifiers are applied to the Button view to customize its appearance, such as background to set the background color and frame to set the size to 200*200.

How does such a few lines of code accomplish the display of a view?
In fact, SwiftUI combines these views and modifiers to form a complete view hierarchy based on the view hierarchy structure and modifier chaining. SwiftUI converts the view hierarchy and modifier chain from the declarative description to the underlying rendering instructions. These rendering instructions are passed to the system, which then performs the actual drawing and rendering operations that are ultimately rendered on the screen.

Below we introduce a few concepts in conjunction with the code above.

1. View Protocol

The View protocol is one of the core protocols in SwiftUI that defines the fundamentals of building user interfaces and how to use them.The source code for the View protocol is as follows:

1
2
3
4
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}

As you can see from the source code, the View protocol has the following key features:

‘associatedtype Body: View’ :
The View protocol uses the associated type associatedtype, which means that types that follow the View protocol must specify a concrete type associated with the View. This concrete type is named Body and must also conform to the View protocol.

‘var body: Self.Body { get }’ :
The View protocol requires that followers must provide a computed property named body. This property returns a view type that is the previously defined association type Body.

The View protocol works by returning a specific view hierarchy via the body property. This view hierarchy can contain other views or components to form a complex user interface, and SwiftUI renders the final user interface based on this view hierarchy.

2. some View

Using ‘some View’ as a return type means that the view can return any type that conforms to the View protocol. This syntax is called opaque return type, a feature that allows the compiler to infer the type of view returned without requiring the developer to explicitly specify it. It means that “an object conforms to the View protocol, but we do not care whether it is a Button or Text, as long as the compiler knows on the line”.

1
2
3
4
5
6
7
8
9
10
11
import SwiftUI

struct ContentView: View {
var body: Button { //This is not a View but a Button.
Button("Hello, world!") {
}
.background(.red)
.frame(width: 200, height: 200)
}
}

The effect of the above code is the same, only the lack of flexibility, each time you need to figure out what type of specific return in the body, too tired, in fact, to the compiler on the line.

‘some’ is a special generic type placeholder in Swift that supports more complex combinations and reuse of multiple view types, making SwiftUI code more flexible and concise and reducing the possibility of type errors, which is also a reflection of SwiftUI’s polymorphism.

3. Modifier

Let’s take a look at the effect of executing each of the following two types of code:

Attribute modifier code after switching the order:

Just adjusting the order in which the two lines of code are executed results in a different outcome, which almost turns our perception upside down.

Here we need to understand the working mechanism of Modifier in SwiftUI, unlike UIKit where each modifier acts as an object, you can simply think of each modifier in SwiftUI as “working alone”, for example, after modifier A modifies an object, it will become another object after modifier B modifies it. For example, after modifier A is modified, it forms an object, and after it is modified by modifier B, it becomes another object.

1
2
3
4
5
Button("Hello, world!") {
print(type(of: self.body))
}
.background(.red)
.frame(width: 200, height: 200)

With type you can print the exact type of a value as follows:

1
ModifiedContent<ModifiedContent<Button<Text>, _BackgroundStyleModifier<Color>>, _FrameLayout>

It first generates a Button with text and background color via ‘ModifiedContent<Button, _BackgroundStyleModifier‘. Then, it sets a larger frame to it via ‘ModifiedContent<…, _framayout >’.

So, end up with a stack of ‘ModifiedContent’ types - each of which transforms a view plus the actual changes to be made, rather than modifying the view directly.

Simply, the front Modified A production “product” is like a brick out of the kiln pit has been molded, the back of the Modifier B, Modifier C … Modifier B, Modifier C … can not be changed, only in the original basis of “stacking”.

Reference

[1] Why modifier order matters
[2] Why does SwiftUI use “some View” for its view type?