February 12, 2020

Your first SwiftUI screen (2/7)

Marin Benčević

SwiftUI is a unifying framework in more ways than one. Whether you're building an Apple Watch, Apple TV, iPhone, iPad or even Mac apps, SwiftUI will let you do that. Whether you're a designer, web developer or simply someone interested in building apps, SwiftUI is still for you. It might take a bit longer than for seasoned iOS veterans, but you'll get the hang of things!

Once you're on the other side of this course, you won't just be an iOS developer  you'll be a SwiftUI developer. Capable of building for all Apple platforms!

This course will guide you through building a real-world chat application. You'll build each part of the app including logging the user in, displaying their contacts and chatting over the internet. By building a real app, you'll quickly learn practical SwiftUI skills that you can use in the real world.

In this first part, you'll make a welcome screen for the app. You'll learn about existing SwiftUI views, how to make your own views, how to lay them out and space them as well as how to style them to your liking.

On your way to victory, SwiftUI might try to sabotage you at some point with its opaque error messages or hard-to-find APIs. Remember, fortune favors the brave, so let's dive in with courage!

You can find the finished project of this part of the SwiftUI course here.

Making SwiftUI Views

We'll start our SwiftUI adventure by building out a welcome screen for our app. It will be a static screen with some information, an image and buttons to either log in or sign up to our chat service.

Your first SwiftUI screen!

A SwiftUI app is a huge tree of Views. View is a SwiftUI protocol that represents any view that can be shown on the screen, whether it's a huge detail screen or a simple label.

SwiftUI View hierarchy

Let's start by creating a new SwiftUI app. Open up Xcode. For this to work, you'll need Xcode 11 or later.

On the Xcode welcome screen, click Create a new Xcode project. Select the Single View App template under iOS and hit Next. Name the app anything you'd like. I named my app "CometChat". Make sure SwiftUI is selected as the User Interface and click Next again. Select a good location to save the project and click Create.

Creating a new SwiftUI app in Xcode

Congrats! You just made your first SwiftUI app. This is the end of this course. Just kidding.

Next, you'll create a new SwiftUI view. Click File > New > File... and, as the template, select SwiftUI View and name it WelcomeView.

You'll get presented with an almost empty struct, except for one computed property called body. This is the most important part of a view. body returns the contents of your view. iOS will periodically call this computed property to re-draw what's on the screen.

You might have noticed body's type is some View. View is a protocol, and the some keyword tells Swift that body can be any concrete type, as long as it conforms to View.

Change the contents of the struct to the following:

{% c-block language=“swift” %}
struct WelcomeView: View {
 var body: some View {
   Text("Create an account")
 }
}
{% c-block-end %}

On the right of your code, you'll see the Canvas. This is where you can preview the views you're making. If the preview updating is paused, click the Resume button in the top-right corner.

Your first SwiftUI view!

As you make changes to your code, the Canvas will update to show your changes. At least in theory. Often the canvas will be too quick to update and try to compile partially-written code or run into some other issue. You'll get used to clicking Try Again and Resume a lot. :)

Arranging SwiftUI views with stacks

Let's add another label to the view, below the one you just added. You might be tempted to simply add another Text below the current one, but if you do that you'd get a compiler error. The error happens because body is a single View: You can't return two values. Instead, you need a view that would wrap around the two texts, like a SwiftUI birthday present.

SwiftUI has a couple of different views that group other views and the one you'll use the most is VStack and HStack. These are the SwiftUI equivalents to UIKit's UIStackView, stacking for vertical and horizontal stack views, respectively. If you're coming from web development, VStack and HStack are the Flexbox of SwiftUI.

These two views are your primary layout tool in SwiftUI. By arranging items into stacks, nesting stacks within stacks and controlling stack alignment and item spacing you can make almost any layout imaginable.

Let's change body so that it returns a vertical stack of two text views:

{% c-block language=“swift” %}
var body: some View {
 VStack {
   Text("Create an account") Text("Connect with people around the world")
 }
}
{% c-block-end %}

You'll see the two labels centered in the middle of the preview screen.

A SwiftUI VStack

If you look at the screen we're trying to make, though, the two texts need to be aligned to the left and sit on top of the view. You can align items in a stack by passing the alignment in the stack's initializer:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account") Text("Connect with people around the world")
 }
}
{% c-block-end %}

Now the texts are aligned to the left, but they're still not on the top of the screen.

Left-align items in a SwiftUI VStack

Let's look at how we can fix that.

Vertically aligning VStack items with spacers

The stack's alignment controls the alignment of the items along the opposite axis of the stack, so for VStack, it controls the horizontal alignment. To align items along the major axis you can either manually space the items or use a special view called Spacer.

A Spacer, as its name suggests, is an empty view that stretches out to fill out as much space as it can. When you place it inside a VStack, it will stretch horizontally. If you place it in an HStack, it stretches vertically.

Add a spacer to the bottom of your stack to see what happens:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account") Text("Connect with people around the world") Spacer()
 }
}
{% c-block-end %}

If VStack is the birthday present, the spacers are the birthday party balloons, filling up the horizontal space as long as they can, pushing the two text views to the top of the screen.

To get the feel for how spacers behave, experiment by placing spacers at different positions inside the stack. Here are a couple of examples:

SwiftUI VStack and Spacer cheat sheet

As you can see, by placing spacers at different positions you can align and space out your items anywhere in the stack. To fine-tune the spacing, you'll use padding — but you'll read more on that later in this part of the SwiftUI course.

For now, we'll take a break from laying out our view and venture into styling the text.

Styling SwiftUI views with View methods

Views have all kinds of methods to tweak their properties, whether its changing their size, colors, state, opacity or anything else that can be changed. Some of these methods are common to all views, like methods for changing the frame or padding. Others are specific to that view, such as the font of a Text or the enabled state of a Button.

According to the screenshot of the view we're building, the second text in our stack needs to have a big and bold font. To make this change, call the font method on the text:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account") Text("Connect with people around the world").font(Font.largeTitle.weight(.bold)) Spacer()
 }
}
{% c-block-end %}

If you're used to UIKit, you're probably familiar with classes like UIFont or UIColor. SwiftUI has its equivalents, but drops the UI prefix and makes them a struct.

There's also one big difference between the UIKit and SwiftUI versions: SwiftUI's fonts and colors are late binding. This means that a Font is just a token or an identifier for some font. This token only gets resolved to a specific font during runtime, depending on the environment it's running in. For instance, Font.largeTitle gets resolved to San Francisco on iOS, but could be Helvetica on the web.

SwiftUI modifier methods are non-mutating. Instead, they return a new View with the changes applied. This has two consequences. First, you can chain modifier calls one after the other.

{% c-block language=“swift” %}
Image("welcome")
 .resizable() // returns a new Image
 .aspectRatio(contentMode: .fit) // returns a new View
 .padding(.bottom, 35) // returns a new View
{% c-block-end %}

Secondly, because each of these methods creates a new view, the order of calling the methods can sometimes be important. A modifier called later can override what you set earlier, like in CSS or when subclassing a Swift class.

If you're the kind of developer who prefers a GUI over plain code, you can Control-Option-Click a SwiftUI view (either in the code or the preview) and open the SwiftUI Inspector.

SwiftUI Inspector in Xcode

This opens up a popup window that will let you tweak the settings of a view. As you make changes, SwiftUI will generate code for those changes and add them to your body.

Changing the color of a SwiftUI Text

While we're beautifying our views, why not add some color. As you can guess by now, adding color is a simple matter of calling a method on the label:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account") Text("Connect with people around the world").font(Font.largeTitle.weight(.bold)).foregroundColor(.accentColor) Spacer()
 }
}
{% c-block-end %}

Just like Font, Color is also a late-binding token, which means that the actual color value of the color above will depend on the current environment, including what platform it's running on as well as whether it's running in light or dark mode.

Even SwiftUI Colors with specific names like .blue will result in a different color in light and dark mode. These are called adaptive colors, and you can make your own!

Adding your own adaptive colors

You'll add a new color for the text content of your app. Open Assets.xcassets in the main folder of your app in Xcode. Right-click on the list of assets and select New Color Set. At first, you'll see a white color with "Universal" below it. But, if you take a look at the Attributes Inspector, you'll see there's a bunch of different environment combinations you can set.

Adding a SwiftUI adaptable color for dark mode

For instance, you can set one color for CarPlay in dark mode, a different color for Apple TVs in dark mode, and a third color for iPhones.

For now, select only Universal in the Devices list, and Any, Dark in the Appearances dropdown. This allows you to set a different color for light and dark mode on all devices.

Select the Any Appearance color and in the Attributes Inspector select 8-bit Hexadecimal for the Input Method and enter the hex value #2D313F.

Adding a SwiftUI adaptable color for dark mode

For Dark Appearance plain white will suffice. Select the Color in the sidebar and rename it to body. This name is important because that's how we'll access it in code.

Create a new plain Swift file called Colors.swift. Replace the contents of the file with the following:

{% c-block language=“swift” %}
import UIKit
import SwiftUIextension Color {
 static
 let body = Color("body") static
 let cometChatBlue = Color(red: 27 / 255, green: 71 / 255, blue: 219 / 255) static
 let shadow = Color(red: 27 / 255, green: 71 / 255, blue: 219 / 255, opacity: 0.3) static
 let background = Color(red: 248 / 255, green: 249 / 255, blue: 251 / 255)
}
extension UIColor {
 static
 let body = UIColor(red: 45 / 255, green: 49 / 255, blue: 63 / 255, alpha: 1)
}

{% c-block-end %}

This adds new static properties to Color and UIColor. We can use these colors throughout our app without having to copy and paste color names or specific values. Having everything in one place also means we can easily change these colors later.

Now that you have the color you can use it to make your text prettier. Head back to WelcomeView.swift and add a color to the first text:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account")
   .foregroundColor(.body)
   Text("Connect with people around the world")
   .font(Font.largeTitle.weight(.bold))
   .foregroundColor(.cometChatBlue) Spacer()
 }
}
{% c-block-end %}

Adding a SwiftUI adaptable color for dark mode

Extending Color with static properties is a great way to add reusability to our app. You can go a step further to make any changes to a view reusable. Let's do that next.

Styling SwiftUI views with view modifiers

So far you have a title and a body text styled as they look in the screenshot above. You'll use the same text treatment (font and color combination) throughout your whole app. Instead of copying and pasting all the method calls in every View with texts, it would be better to have a way to mark titles and body text in a more reusable way.

SwiftUI lets you do that with View Modifiers. Structs and classes in your app can implement the ViewModifier protocol which has one required method that receives and returns a view. The idea is that you receive a view, perform any necessary transformations and return it back to the caller.

You'll add two modifiers, one for the body text and one for titles. Create another plain Swift file and name it TextModifiers.swift. Add a new view modifier to the file:

{% c-block language=“swift” %}
import SwiftUI
struct TitleText: ViewModifier {
 func body(content: Content) - > some View {
   content.font(Font.largeTitle.weight(.bold)).foregroundColor(.cometChatBlue)
 }
}
{% c-block-end %}

You implement the required method by receiving a view, applying a new font and foreground color and returning the changed view.

Similarly, add another struct below the one you just created for the body text:

{% c-block language=“swift” %}
struct BodyText: ViewModifier {
 func body(content: Content) -> some View {
   content
     .foregroundColor(.body)
 }
}
{% c-block-end %}

For the body text, you'll leave the font as it is and only change the color. Now, you can go back to WelcomeView.swift and replace the styling you applied with the modifiers by calling the modifier method on the text views:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account")
   .modifier(BodyText())
   Text("Connect with people around the world")
   .modifier(TitleText()) Spacer()
 }
}
{% c-block-end %}

You can now use these modifiers in any future views. When you want to change the look of your apps content, you can change the modifier the apply the changes everywhere in your app.

Since view modifiers return a View, you have the complete power of SwiftUI inside a modifier. This means you can even do complex changes like embed the view in a stack, scale it, adjust its spacing, add overlays and make whichever changes you can think of.

Spacing SwiftUI views with padding

I think that's enough styling for now. It's time to get back to laying out our screen with padding. SwiftUI doesn't have Auto Layout or constraints, but that doesn't mean you can't do everything you did in UIKit. It might be even easier. At least sometimes.

The method you use for this is called padding, and it has two optional parameters:

{% c-block language=“swift” %}
func padding(
 _ edges: Edge.Set = .all,
 _ length: CGFloat? = nil)
 -> some View
{% c-block-end %}

The first parameter is the set of edges to apply the padding to, while the second is the actual value of the padding. By default, the first parameter applies the padding to all edges. If you omit the length, it will be nil which will apply the default system padding to the view.

Usually, you'd want to apply the default padding. The advantage of the default padding is that it's automatically adjusted to the device you're using. Smaller devices will have a smaller padding and vice-versa.

Change the body of your view to add a padding to both of the texts:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account")
   .modifier(BodyText())
   .padding()
   Text("Connect with people around the world")
   .modifier(TitleText())
   .padding([.bottom, .leading, .trailing]) Spacer()
 }
}
{% c-block-end %}

To the body text, you add a default padding to every edge by calling padding(). The title text doesn't need a padding on top, so you add the system padding only to the bottom, leading (left) and trailing (right) edges.

Adding padding to a SwiftUI view

You can combine the first and second parameter of padding to achieve your desired effect. Here are some examples of different ways you can call padding:

  • padding() applies system padding to all edges.
  • padding(.leading) applies the system padding to the left edge.
  • padding([.bottom, .leading, .trailing]) applies the system padding to three edges.
  • padding(.top, 20) adds a padding of 20 points to the top edge.
  • padding([.bottom, .top], 0) makes the top and bottom padding zero.

By combining stacks and padding you can intuitively make pixel-perfect layouts. At least, once you get used to it. :)

Nesting SwiftUI stacks

In UIKit, one of the most common view types are stack and table views. The same goes for SwiftUI: Your views will often be a big tree of nested stacks. In the screenshot of the screen you're making there's an image with a text below the two texts you just created.

Both the image and the text are centered, so add another VStack inside the existing one, that you'll center horizontally:

{% c-block language=“swift” %}
var body: some View {
   VStack(alignment: .leading) {
     Text("Create an account")
       .modifier(BodyText())
       .padding()
     Text("Connect with people around the world")
       .modifier(TitleText())
       .padding([.bottom, .leading, .trailing])
     VStack(alignment: .center) {
       Text(""
         "
         This is a sample app.Create an account or login to begin chatting.
         ""
         ")
         .modifier(BodyText())
         .multilineTextAlignment(.center)
         .padding([.leading, .trailing], 40)
       }
       Spacer()
     }
   }
{% c-block-end %}


You add another VStack that will hold the image and the text. The text also has the body text modifier. You make sure the text is centered and add a padding of 40 points on the leading and trailing edges, so it doesn't touch the edges of the screen.

SwiftUI Nested VStacks

Displaying images in SwiftUI

Now that we added the text that's below the image, it's time to add the image. Image views in SwiftUI are called Image, and they can be tricky to parse at first.

Before you add an image view, you first need to add the image itself. You can find the welcome image here.

Open Assets.xcassets and drag the image into the list of assets. Make sure the name is welcome.

Now, you can add a new Image to the inner VStack:

{% c-block language=“swift” %}
VStack(alignment: .center) {
   Image("welcome")
     .padding(.bottom, 35)
     .padding([.leading, .trailing], 80)
   Text(""
     "
     This is a sample app.Create an account or login to begin chatting.
     ""
     ")
     .modifier(BodyText())
     .multilineTextAlignment(.center)
     .padding([.leading, .trailing], 40)
   }
{% c-block-end %}

You give the Image the name of the asset you created. Give it a padding of 35 points at the bottom (between the image and the text), as well as a padding of 80 on the sides.

The image looks good, but there's a small issue: Our whole UI is now broken.

Making an Image resizable in SwiftUI

It would be ideal if the image scaled according to the device size and how much space it has to grow. On an iPhone SE, the image should be smaller than on an 11 Pro Max.

We'll deal with this issue in a second, but let's first take a look at how we can spot issues like this in the future.

Previewing SwiftUI views on a different device

You can tweak the SwiftUI preview to see your views at different sizes. The preview screen isn't magic: It's loaded from a struct in the file that implements PreviewProvider. The preview provider is similar to a View. It has one computed property where you return what should be drawn inside the Canvas.

{% c-block language=“swift” %}
struct WelcomeView_Previews: PreviewProvider {
 static var previews: some View {
   WelcomeView()
 }
}
{% c-block-end %}

You can change this view in the same way you can change any other View. There are also preview-specific methods that control the size and look of the previews.

Call previewDevice inside the previews property and display an iPhone SE:

{% c-block language=“swift” %}
struct WelcomeView_Previews: PreviewProvider {
 static var previews: some View {
   WelcomeView()
     .previewDevice(PreviewDevice(rawValue: "iPhone SE"))
 }
}
{% c-block-end %}

Making an Image resizable in SwiftUI

You can now see that the image doesn't scale according to screen size. Let's fix that.

Scaling a SwiftUI Image

To make an Image view resizable you need to, you guessed it, call the resizable method! Add the following two new lines to body:

{% c-block language=“swift” %}
VStack(alignment: .center) {
   Image("welcome")
     .resizable()
     .aspectRatio(contentMode: .fit)
     .padding(.bottom, 35)
     .padding([.leading, .trailing], 80)
   Text(""
     "
     This is a sample app.Create an account or login to begin chatting.
     ""
     ")
     .modifier(BodyText())
     .multilineTextAlignment(.center)
     .padding([.leading, .trailing], 40)
   }
{% c-block-end %}

By default the image will just scale to fill its available space, which you probably don't want. Usually, you want to maintain the image's aspect ratio so that it doesn't look stretched out.

To do that in SwiftUI, you can use the aspectRatio method which receives a parameter telling it how to scale the image. .fit scales the image so that it always fits inside its space, leading to letter-boxing. On the other hand, .fill scales it so that it fills the whole space, but you won't see the whole image.

In the above code, you first call resizable on the image to tell SwiftUI that it can stretch out the image if needed. Then, you call aspectRatio with .fit to make sure the image always fits within the screen, 80 points from each edge.

Making an Image resizable in SwiftUI

If you change the device back to iPhone 11 in the preview you'll notice the image now scales to fit its available size, no Auto Layout needed.

We're almost done with our welcome screen! The final step is to add the two buttons on the bottom.

Creating buttons in SwiftUI

To build out a screen like this in UIKit, you'd use a view controller, and house all of your view related things in there. Usually, this is not how you'll build SwiftUI views.

SwiftUI does away with the distinction between view and view controller and it's built so that you compose tons of tiny views to build your screen.

That's what you've been doing: Text, VStack and Image are all small views that do one thing well, but by combining them you've created an already good-looking screen.

Now it's time we make your own small SwiftUI views that you can use throughout the app. These views will be the two buttons you see on the bottom of the screen you're building.

Creating a Button in SwiftUI

Create a new SwiftUI View file called ButtonViews, and change the struct to the following:

{% c-block language=“swift” %}
import SwiftUI
struct PrimaryButton: View {
 let title: String
 var body: some View {
   Text(title.uppercased())
 }
}
{% c-block-end %}

You need to build a big blue button with rounded corners. You'll start with a good old text view.

A button can hold any other view, no matter how complex. With that in mind, our button is looking a little plain.

At this point, you'll get an error because the automatically generated preview references the wrong view name. Change the preview to show your button:

{% c-block language=“swift” %}
struct ButtonViews_Previews: PreviewProvider {
 static var previews: some View {
   PrimaryButton(title: "Primary")
 }
}
{% c-block-end %}

Creating a SwiftUI Button

We'll make it look better, but before we go on, let's do some preview setup to make our lives easier.

Changing the size of a SwiftUI preview

Earlier in this section of the course, you learned how to preview your SwiftUI views on different devices. Most of your views will be reusable UI components, so it doesn't make sense to show a whole iPhone with a tiny view in the middle of a blank screen.

Instead, you can preview them in a small window. Change the preview struct to the following:

{% c-block language=“swift” %}
struct ButtonViews_Previews: PreviewProvider {
 static var previews: some View {
   PrimaryButton(title: "Primary")
     .previewLayout(.fixed(width: 300, height: 100))
 }
}
{% c-block-end %}

Now the preview shows a small rectangle that houses our button.

Changing the preview size in SwiftUI

Nice and neat! Let's get back to our buttons.

Styling SwiftUI buttons

Let's deal with that plain-looking button! Start by adding a background and a shadow to the button:

{% c-block language=“swift” %}
var body: some View {
 Text(title.uppercased())
   .background(Color.accentColor)
   .cornerRadius(5)
   .shadow(color: .shadow, radius: 15, x: 0, y: 5)
}
{% c-block-end %}

Okay, you now have a rounded blue blob, which is a start.

Making a Button in SwiftUI

Next, change the color and add some padding around the text:

{% c-block language=“swift” %}
var body: some View {
 Text(title.uppercased())
   .fontWeight(.bold)
   .foregroundColor(.white)
   .padding()
   .frame(maxWidth: .infinity)
   .background(Color.accentColor)
   .cornerRadius(5)
   .shadow(color: .shadow, radius: 15, x: 0, y: 5)
}
{% c-block-end %}

There's one new method here: frame. While padding determines how much space there is around the content, the frame determines how big the content is.

You can either specify a constant size or you can make suggestions to SwiftUI about how it should size the view. Here, you set the maximum width to infinite, making the view stretch to fit the size of its parent.

Making a Button in SwiftUI

Our button is looking perfect! Let's add another, secondary button.

Creating the secondary button

To create the secondary button, you'll use a complex programming technique used by generations of programmers: copying and pasting. Because SwiftUI views are short pieces of code that are all in a single struct, you can easily copy and paste a view, modify it slightly and get a whole new look.

For the secondary button, copy over the primary button and change the colors a bit to match the screenshot:

{% c-block language=“swift” %}
struct SecondaryButton: View {
 let title: String
 var body: some View {
   Text(title.uppercased())
     .fontWeight(.bold)
     .foregroundColor(.accentColor)
     .padding()
     .frame(maxWidth: .infinity)
     .background(Color.white)
     .cornerRadius(5)
     .shadow(color: .shadow, radius: 15, x: 0, y: 5)
 }
}
{% c-block-end %}

It looks great... I think. To see the button you need to change your preview first.

Showing multiple SwiftUI previews

You might be tempted to replace Primary with Secondary in the preview and call it a day. But, why show just one view, when you can show both of them at once?

If you wrap the views inside a Group, the Canvas will show each grouped view as a separate preview.

{% c-block language=“swift” %}
static var previews: some View {
 Group {
   PrimaryButton(title: "Primary")
     .previewLayout(.fixed(width: 300, height: 100))
   SecondaryButton(title: "Secondary")
     .previewLayout(.fixed(width: 300, height: 100))
 }
}
{% c-block-end %}

Now you can see both of the buttons at once.

Previewing two SwiftUI views at once

This is especially useful when you have views that can show multiple states. For instance, we can show a preview for buttons that have long titles:

{% c-block language=“swift” %}
static var previews: some View {
 Group {
   PrimaryButton(title: "Primary")
     .previewLayout(.fixed(width: 300, height: 100))
   PrimaryButton(title: "This button has a really long title")
     .previewLayout(.fixed(width: 300, height: 100))
   SecondaryButton(title: "Secondary")
     .previewLayout(.fixed(width: 300, height: 100))
 }
}
{% c-block-end %}

Showing multiple View states in a SwiftUI preview

This makes it easy to see how your changes affect different states of your views, making sure you spot a lot of bugs without having to build and click around your app.

Using custom SwitUI views

With our buttons created, let's add them to the welcome screen. Back in WelcomeView.swift, add a new VStack with the two buttons, the final addition to the welcome screen:

{% c-block language=“swift” %}
var body: some View {
 VStack(alignment: .leading) {
   Text("Create an account")
     .modifier(BodyText())
     .padding()
   Text("Connect with people around the world")
     .modifier(TitleText())
     .padding([.bottom, .leading, .trailing])
   VStack(alignment: .center) { ... }
   // New code:
   VStack(spacing: 30) {
     Button(action: { }) {
       PrimaryButton(title: "Log In")
     }
     Button(action: { }) {
       SecondaryButton(title: "Sign Up")
     }
   }
   .padding([.leading, .bottom, .trailing])
   .padding(.top, 40)
   // ---
   Spacer()
 }
}
{% c-block-end %}

You can use your Views just like any other SwiftUI view. After all, they're nothing more than a Swift struct conforming to a protocol. In the above code, you placed your views inside a VStack, and each inside a Button.

As opposed to UIKit's UIButton, Button is a completely blank view. All it does is make its contents touchable and manages button state changes. How the button looks is completely up to you. In the code above, you used PrimaryButton and SecondaryButton to display a button label. Instead of that, you could have used Text, Image or any other SwiftUI view.

Currently, the buttons don't do anything. But, if you run the project and tap on the buttons, you'll notice they light up as you tap them. That animation is coming from Button.

Earlier in this part of the course you used padding to space out views within a stack. If you want the space between each of the items to be the same, you can pass the spacing parameter to a stack's initializer. The stack will then take care of the spacing for you. You can even use padding a spacing in tandem for those pixel-perfect designers among you!

Your first SwiftUI screen!

And with that, your welcome screen is done!  This now looks exactly like the original screenshot, giving the user some information about the app and a way to log in and register. Right now, these buttons don't do anything. In the next part of this SwiftUI course, you'll navigate to a login and registration screen!

Conclusion

In the span of one part of this course, you went from having no app and no SwiftUI knowledge to building out your first full SwiftUI screen! With this knowledge and some googling, you now have the skills to build almost any static SwiftUI screen.

You know about views, how to create them, lay them out and space them around. You also learned about adding and customizing text, creating buttons and displaying images.

SwiftUI shows its power here: By learning a small number of tools you can build out any UI you can imagine. That's elegant, flexible design.

Your layout knowledge doesn't need to end here, though. For the A-grade students among you, check out the WWDC session called "Building Custom Views with SwiftUI" which goes more in-depth into how the layout system works and some other tricks you can use.

There's still much to learn! You now know how to make static UIs, but in the next part, you'll learn how to navigate to a login screen and manage the state for your UIs. Keep reading to build out the login screen of your app!