The View object

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.

figure

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:


import UIKit

protocol Umanoide {
}

struct Donna : Umanoide {
}

struct Uomo : Umanoide {
}

func bambino() -> Umanoide {
	Bool.random() ? Donna() : Uomo()
}

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 primogenito = bambino()
let secondogenito = bambino()

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


print((primogenito == secondogenito))

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:

figure

Fig.2 - L'output del playground.

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 Umanoide : Equatable {
}

struct Donna : Umanoide {
}

struct Uomo : Umanoide {
}

func bambino() -> some Umanoide {
	Uomo()
}

func bambina() -> some Umanoide {
	Donna()
}

let primogenito = bambino()
let secondogenito = bambina()

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.

For the more curious - Opaque types in Swift 5.1

Modifiers

We have seen how to create a View object and how to satisfy the minimum requirements of the View protocol for our custom objects. But to personalize an object, tipically, it is necessary to act on basic properties: character, color, shadows, layout, etc.

SwiftUI provides us with a simple and powerful concept to satisfy this requirement, called modifiers.

figure

A modifier is simply a method that creates a new view from an existing view. A tree representation could be the following:

figure

Fig.2 - Simple hierarchy for the .font modifier

Furthermore, this behavior can be repeated several times:

figure

Fig.3 - Modifiers can be nested.

Modifiers are also View constructors that modify the behavior of the previous element through a mechanism called chaining.

At first glance, if we are used to working with UIKit, this mechanism may seem inefficient and unorthodox: we are having multiple views that do the same thing, changing only some of its properties. But we must remember that in SwiftUI everything consists of structs.

Structures are highly efficient in terms of creation and memory allocation because they work directly on the memory stack and not on the heap as the corresponding classes. Furthermore, we must not forget that we are facing with a new declarative paradigm and this behavior is perfectly normal - indeed, it is the one recommended by Apple itself to work with views.

The compiler, behind the scenes, will be able to adapt our requests (color, font, etc.) to an efficient data structure without us having to worry about performances.

For the more curious - Class and Struct in Swift

In this first article, we saw the basic concepts behind SwiftUI. This new framework is still in a first early beta and will certainly undergo major changes and improvements before the final release with iOS13 this fall. My advice is to continue to follow it, study it and experiment. Like everything new, it must be explored and tested in practice, before jumping to any kind of conclusion.


I wish you good work!

Next article: SwiftUI - Building complex Views

© Copyright 2019 Sofapps - All Rights Reserved - VAT 05197020877