SwiftUI Programming: The View Object

Jan 06, 2020 · 6 mins read · Costantino Pistagna · @valv0

SwiftUI Programming: The View Object

We have seen that SwiftUI is a declarative framework. The visual result of what we see in our device is the result of a series of statements we make to our compiler about how we would like to see our interface. But if the UIView was the beating heart in UIKit, what is the equivalent in SwiftUI? The answer is a new object, called View.

What is a View?

The first real and big difference compared to a UIView is the fact that in SwiftUI a View is a struct that conforms to a protocol. The basic example provided by Apple begins this way:


struct ContentView: View {
   var body: some View {
      Text("Hello World")
   }
}

The one above is a fully functional SwiftUI object named ContentView and which contains (returns) a label with “Hello World” in its body.

You will notice that I talked about the concept of “containing” (return was only hinted at in brackets). In fact, one of the cornerstones of SwiftUI is the reusability of the code and the concept of View reusable as if they were Lego ™ bricks.

Fig.1 - Tree like hierarchy about the above example.
Fig.1 - Tree like hierarchy about the above example.

A view, therefore, defines a piece of our User Interface.


Everything in an app in SwiftUI can be traced back to a View. The latter defines, therefore, a piece of our interface. As a consequence, complex views are constructed from elementary bits that cooperate and work together creating a tree hierarchy. Behind the scenes, the compiler handles all the Views an translates them in a highly efficient data structure that display the required content on screen. But how does this composition work?

One only requirement: body

We said above that a View is a struct that conforms to the View protocol. The only requirement of this protocol is to provide our View with a body responsible for the content of our object. The body is a special closure that returns a content also in the form of a View. For example, in the code above provided by Apple:


...
   var body: some View {
      Text("Hello World")
   }
...

In the above case, the body of our View is a basic component, called Text, which in turn returns an object conforming to the some View type request expressed in the body. This syntax is part of a new concept presented with Swift 5.1 called Opaque Types. For now it is enough to know that the opaque type required by the body, allows us to effectively interchange objects of which we do not know the specific type in advance but which is well determined at the level of compilation (unlike a simple protocol).

Opaque types

Returning an opaque type seems very similar to using a protocol. However, these two constructs differ in the fact that the opaque type preserves the type. An opaque type refers to a specific type, even if the caller of the function is not able to see which type it is. A protocol, on the other hand, can refer to any type that conforms to it. The latter offer more flexibility with respect to the returned type. The opaque types, on the other hand, allow us to have specific guarantees regarding the returned type.

Let’s try to give an example, to clarify the concept better:

mport UIKit

protocol Humanoid {
}

struct Woman : Humanoid {
}

struct Man : Humanoid {
}

func child() -> Humanoid {
	Bool.random() ? Woman() : Man()
}

The Humanoid protocol is adopted by two structures: Man and Woman. The child function, returns a Humanoid type that can be equally probable one of the two cases mentioned. At this point, we create two humanoid instances:


let first = child()
let second = child()

and since we are curious to know if our children are male or female, let’s try to compare them:


print((first == second))


The compiler, at this point, will blame us - rightly - about the fact that the humanoid class is not comparable because it does not adopt the Equatable protocol. Not bad, you’ll think. Let’s try to adopt it:

Fig.2 - Playground output.
Fig.2 - Playground output.

Problem is that Humanoid cannot be compared because Swift has no guarantee that the returned items are of a comparable type. I recall that any structure can adopt the protocol, by implementing the required methods.

To solve this problem, opaque types are born. The code above can be transformed as follows:


import UIKit

protocol Humanoid : Equatable {
}

struct Woman : Humanoid {
}

struct Man : Humanoid {
}

func boy() -> some Humanoid {
	Man()
}

func girl() -> some Humanoid {
	Woman()
}

let primogenito = boy()
let secondogenito = girl()

An important observation about the code above is that, as you will notice, I had to create two different children() functions (one for female - bambina and one for Male - bambino) because one of the main requirements when working with an opaque type is that the methods must return only one type of object. You will find this concept very often in SwiftUI when we will work with Views.


Costantino Pistagna
Costantino Pistagna · @valv0 Costantino is a software architect, project manager and consultant with more than ten years of experience in the software industry. He developed and managed projects for universities, medium-sized companies, multi-national corporations, and startups. He is among the first teachers for the Apple's iOS Developer Academy, based in Europe. Regularly, he lectures iOS development around the world, giving students the skills to develop their own high quality apps. While not writing apps, Costantino improves his chefs skills, travelling the world with his beautiful family.