SwiftUI : A Brief Introduction on View

Jimmy Liu
Kuo’s Funhouse
8 min readOct 16, 2019

--

As amazing as WWDC 2019 showed us about SwiftUI, I can’t seem to wrap my head around how SwiftUI works.

In order to clear up my mind, I have decided to take a deep look on what exactly is a View and how do we modify it.

View

In SwiftUI, when we are creating an interface, we need to wrap its content in the body within a View. So the millionaire question is:

What is a View ?

SwiftUI

View is a protocol that contains a get only variable called body, which has a type called Body, in this case Body is a type that adapts View protocol.

So how is it implemented?

Let’s take a look at the default SwiftUI template given by Apple:

Here, the ContentView is a struct that adapts View protocol. What is interesting, is that the body is adapting some View instead of View.

some is an opaque type, it acts like a reverse generic, meaning the return type is determined by the caller instead by callee. For more info, please click here.

Why is it some View instead of View ?

If we changed some View to View :

You will get two errors :

Type 'ContentView' does not conform to protocol 'View'
and
Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements

Since var body in View is declared as Self.Body, it is generic by default.

By declaring as some View, the type of body is then determined by whatever View type is placed in it. That’s why we can put a something as simple as a label (Text) to something complex like a TableView (List) inside the body.

Since body can only accept a single return, if we put more than one View inside, an error would occur :

Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type

This can be solved by simply add return before one of the View :

However, if we wish to show more than 1 components, we need to a container to hold them. The two main containers are VStack (Stacks views Vertically) and HStack (Stacks views Horizontally). So let’s take a look at VStack.

VStack

SwiftUI VStack

In the code, you will find that Body is set to Never , and the init contains a content argument, which is a ViewBuilder.

A ViewBuilder is a custom parameter attribute that constructs views from closures.

Typically ViewBuilder is used as a parameter attribute for child view-producing closure parameters, allowing those closures to provide multiple child views.Example :
func contextMenu<MenuItems : View>(
@ViewBuilder menuItems: () -> MenuItems
) -> some View
myView.contextMenu {
Text("Cut")
Text("Copy")
Text("Paste")
if isSymbol {
Text("Jump to Definition")
}
}

Basically, a ViewBuilder acts as multiple bodies. But why do we need a ViewBuilder? Let’s take a look what does Never do :

Never is a return type used for closure, function, or method that unconditionally throws an error, traps, or otherwise does not terminate.

This means that body of a VStack is useless, instead its function is now replaced by content, a ViewBuilder .

When is “Never” used ?

The easy version is when the component do not need a body to operate.

Let me explain it to you here :

From what we’ve seen so far, every structure that adapts View has a body variable. When displaying the body of a View, SwiftUI will ask for the body of the subview inside a body and this goes on forever until no body is found.

In order to terminate the loop earlier, SwiftUI uses Never to let caller know that there is no need to go further.

In the case of VStack, since it is a container that can hold multiple views, a body which only returns a single View is not suitable for VStack, hence not needed. Therefore, Body is set to Never instead.

A better answer is given by Rob Mayoff.

Knowing how a View works, let’s take a look at what else can be performed on View.

What else can be performed on a View ?

If you look into View in SwiftUI, you will find that there are many build-in functions for View.

These functions include : datePickerStyle, sheet (presents sheet), modifier, onLongPressGesture, overlay, border , compositingGroup and much more.

Let’s try to make a simple Text like the one below :

Round-Edged Label

Initialization of Text

In this example, the main View that we are using is Text.

A Text is a struct that adapts Equatable as well as View.

There are three ways to initialize a Text :

The easiest way is to use the second method.

Text Property

Next, let’s take a look at what can be done on Text. In the extension of Text, you can see that we can manipulate the properties of the text, such as foregroundColor, font, fontWeight, strikethrough, underline, kerning, tracking, baselineOffset, bold and italic.

Let’s make the font bold and foreground color to white :

Next, we need to use functions from View to modify the Text.

View Modification

When examining View, we found that we can set actions when the View appear/disappear, we can set the background, we can modify the View with modifier, we can scale the View, set gestures on the View and so much more can be done.

Here, we need to set the background of Text :

Things seems great, now we need to set Text to round edged. In order to do so, we need to clip the Text with a Rounded Rect shape :

Now, we’ve notice the border is a bit too close to the actual text. So we need to add some paddings :

Notice how padding is before background.

This is because in SwiftUI, order matters. The latter function will acts upon the result from the prior result.

Here the result is given by padding:

So after the proper inset is done, then a background, will be placed with the new frame below the text :

Rainbow Layers

Now the Text looks great, it’s time to draw the rest of the rainbow colors.

The rest of rainbow colors are layers and layers of View stacking on top of Text. In order to create them, we’ll need to use an overlay.

an overlay Layers a secondary view in front of this view.

In this case, the secondary view is RoundedRectangle.

You might be wondering why not just use a border? Well, unfortunately, in the new SwiftUI, border can no longer be defined with corner radius.

In overlay, it has a parameter called overlay that is an Overlay type, and as stated, Overlay must adapt to View protocol. So the question is, what should we put in there ?

Let’s put another RoundedRectangle in it and we got :

And now you know what RoundedRectangle looks like, lol.

Some of you might be thinking of clipping it off, unfortunately, clipShape does not work like that, as shown below :

What clipShape does is masking the view with the shape stated. That’s why by clipping the overlay with RoundedRectangle gives you the same result.

Instead of clipShape, we need to use stroke:

stroke returns a stroke copy of self :

Next, we need to move to the next layer and this is done with the help of padding:

So we can simply copy and paste for the next 4 layers. However, make it easier for us.

First let’s define an array with all rainbow colors :

Then we can replace the colors with this array :

Then, finally

Done.

Here is another example just to let you know that ORDER is very important in SwiftUI.

We can also scale a View or rotate it :

What is interesting is SwiftUI takes ordering really seriously. For example, if rotationEffect is placed right after Text, then I would get an error :

What happened here is that rotationEffect actually return some View and some View does not have a function called fontWeight , which returns a Text .

Similarly, if I put rotateEffect after fontWeight :

Then only the Text is rotated.

Summary

SwiftUI is an amazing framework, it really saves tons of time to create UI, even the complicated ones (I will demonstrate in the future).

Even though document for SwiftUI is not yet complete, but you can learn a lot by going through the code in SwiftUI framework.

Last but not least, hands on experience is a must. You should definitely go through the tutorial given by Apple and understand how every single command works and try to make something by yourself.

--

--

Jimmy Liu
Kuo’s Funhouse

App Developer who enjoy learning from the ground up.