Chapter 3
IN THIS CHAPTER
Understanding how SwiftUI views work
Showing an image
Showing a button
Stacking views using VStack and HStack
Tying up loose ends
SwiftUI is Apple's latest development framework for creating an application’s user interface (UI). It helps you to declaratively create the UI for your iOS applications.
In this chapter, I explain how SwiftUI views work. Then I explain the basic structure of a SwiftUI view through the use of the Text
view and the Button
view. I show you how to lay out your views using two of the common stacking views, VStack
and HStack
. Finally, I help you put the finishing touches on the example in this chapter.
To understand the basics of SwiftUI views, you can use the project you created in Chapter 1. If you haven't created that project yet, using Xcode, create a new Single View App project and name it Hello World.
The ContentView.swift
file contains the following statements:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Now you’re ready to dive into the statements in more detail.
The ContentView
struct defines the UI of your screen, and it conforms to the View
protocol. Because it conforms to the View
protocol, it must declare a property called body
of type View
. This body
property needs to return a single instance of View
, which in this case is the Text
view. The Text
view is a graphical view that displays one or more lines of read-only text.
Let's now make some changes to the Text
view:
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
}
}
Figure 3-1 shows the preview automatically updated to reflect the changes in the Text
view.
The next struct, ContentView_Previews
, conforms to the PreviewProvider
protocol. This protocol produces view previews in Xcode so that you can preview your UI created in SwiftUI without needing to run the application on the iPhone Simulator or real devices. Essentially, it controls what you see on the preview canvas. As an example, if you want to preview how your UI will look on an iPhone 8 device, you can modify the ContentView_Previews
struct as follows:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().previewDevice("iPhone 8")
}
}
The preview canvas on the right of Xcode will now display your UI on an iPhone 8. I talk more about the ContentView_Previews
struct in Chapters 5 and 13.
Now that you've changed the content of the Text
view, let’s make some cosmetic changes to it:
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!").font(.largeTitle)
}
}
In the preceding statement, you called a function named font(_:)
. This function is called a modifier. A modifier is a function that you apply to a view or the output of another modifier. largeTitle
is a property belonging to the Font
class; it can be shortened as .largeTitle
.
In this example, you chained the Text
view to call the font(_:)
function. The preceding can also be rewritten as follows:
var body: some View {
let t = Text("Hello, SwiftUI!")
return (t.font(.largeTitle))
}
Notice that this isn't as elegant as the previous statement, where you used chaining to call a modifier after the Text
view.
As a convention, you usually put the modifier on a separate line:
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.font(.largeTitle)
}
}
Every modifier returns a new View
instance, so you can chain them. The following example shows how various modifiers can be chained together:
Text("Hello, SwiftUI!")
.font(.largeTitle)
.bold()
.foregroundColor(.red)
Figure 3-2 shows how the Text
view looks now.
You can visually inspect each of the views in the preview canvas by ⌘ -clicking any of the views. A pop-up appears, as shown in Figure 3-3.
Selecting Show SwiftUI Inspector reveals — drumroll, please — the SwiftUI Inspector (see Figure 3-4).
For example, under the Weight
property (in the Font
section), you can choose Thin
(see Figure 3-5).
Xcode automatically modifies your code:
Text("Hello, SwiftUI!")
.font(.largeTitle)
.fontWeight(.thin)
.bold()
.foregroundColor(.red)
Besides modifying the properties of the view, you can also add modifiers to views visually (see Figure 3-6; be sure to scroll to the bottom of the Inspector).
For example, you can select the modifier named Border
, which adds the statements (with the placeholders for the various parameters) shown in Figure 3-7.
You can now double-click the placeholders on the statement and replace them with the values you want. In this example, the border()
modifier displays a border around the text (see Figure 3-8).
To display an image in your ContentView
, you need to use the Image
view. But before you can do that, you need to have an image in your application. The easiest way to add an image to your application is to drag-and-drop one onto the Assets.xcassets
file (see Figure 3-9). In the example, the image was named weimenglee
.
To display the image, use the Image
view and specify the name of the image located in your Assets.xcassets
file:
var body: some View {
Image("weimenglee")
}
Figure 3-10 shows the image displayed.
You can use modifiers on the Image
view to customize its look and feel. Consider the following statements:
var body: some View {
Image("weimenglee")
.frame(width: CGFloat(300.0), height:CGFloat(300))
.clipShape(Circle())
.overlay(Circle().stroke(
Color.black, lineWidth: 5))
}
The output is shown in Figure 3-11. Here’s what the preceding statements have achieved:
If the image is smaller than the dimension of the frame, it's aligned by default to the center of the frame. The following makes this clear:
Image("weimenglee")
.frame(width: CGFloat(300), height: CGFloat(300),
alignment: .center)
.clipShape(Circle())
.overlay(Circle().stroke(Color.black, lineWidth: 5))
You can set the alignment to something else, like to the bottom right:
.frame(width: CGFloat(300), height: CGFloat(300),
alignment: .bottomTrailing)
The image will now appear aligned to the bottom-right corner of the frame, as shown in Figure 3-12.
What about stretching the image to fill the frame? In SwiftUI, an image is always fixed in size unless you call the resizable()
modifier on it, as the following example illustrates:
Image("weimenglee")
.resizable()
.frame(width: CGFloat(300), height: CGFloat(300))
.clipShape(Circle())
.overlay(Circle().stroke(Color.black, lineWidth: 5))
With the resizable()
modifier, the image will now resize itself to fit the dimension specified by the frame()
modifier (see Figure 3-13).
In iOS, buttons look similar to the Text
view, except that users can tap them to perform some actions. In SwiftUI, creating a button usually starts with a Text
view. You then wrap it with a Button
view, like this:
Button(action: {
// the action to perform here
}) {
Text("This is a button")
}
Notice that the Button
view has a parameter named action
, which uses a closure for its action. You can see how to add some actions to the Button
view shortly.
Like the Text
view, the Button
view is customizable. Let's chain some modifiers to the Text
view located within the Button
view to make the button look more presentable:
Button(action: {
}) {
Text("This is a button")
.fontWeight(.bold)
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(CGFloat(10), antialiased: true)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 1)
)
}
Figure 3-14 shows the final look of your customized button.
The preceding statements added the following to the Text
view:
RoundedRectangle
view with a blue colored stroke of width 1 pointApparently, the real use of a Button
view is to perform an action. So, let's now add some action to the button that we’ve been building:
Button(action: {
if let url = URL(string: "https://www.apple.com") {
UIApplication.shared.open(url)
}
}) {
Text("This is a button")
.fontWeight(.bold)
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(CGFloat(10), antialiased: true)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 1)
)
}
In the preceding, when the action is tapped, you used the open()
method from the UIApplication.shared
instance to launch a web browser to display Apple's website.
In real-life applications, you rarely have a UI with just one view. You’re most likely going to have a combination of multiple different types of views, so you need a way to group them together. Plus, the body
property of the ContentView
must return a single View
object, and this is where stacks come to the rescue.
In SwiftUI, there are three main types of stacks available to group your UI:
HStack
: A horizontal stackVStack
: A vertical stackZStack
: A depth-based stackSuppose you want to build a screen to display a business card. Typically, a business card contains a number of lines of text, with an optional image. In SwiftUI, you can do all this with the following views:
Text("Wei-Meng Lee")
Text("Founder")
Text("http://calendar.learn2develop.net")
Text("[email protected]")
Text("@weimenglee")
Image("weimenglee")
Logically, you might add them to the body
property like this:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Wei-Meng Lee")
Text("Founder")
Text("http://calendar.learn2develop.net")
Text("[email protected]")
Text("@weimenglee")
Image("weimenglee")
}
}
One possible way to group this group of views is to use the VStack
view. The VStack
view arranges its children views in a vertical line:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Wei-Meng Lee")
Text("Founder")
Text("http://calendar.learn2develop.net")
Text("[email protected]")
Text("@weimenglee")
Image("weimenglee")
}
}
}
Figure 3-15 shows the views lined up vertically.
By default, all the views wrapped within the VStack
view are aligned in the center. You can change this default alignment using the alignment
parameter. For example, if you want to align the views to the left, you can use the leading
property:
VStack (alignment: .leading) {
Text("Wei-Meng Lee")
Text("Founder")
Text("http://calendar.learn2develop.net")
Text("[email protected]")
Text("@weimenglee")
Image("weimenglee")
}
Figure 3-16 shows that the views are all now aligned to the left. You can also use the following values for the alignment
parameter:
center
(default)trailing
Besides the VStack
view, you can also use the HStack
view, which aligns views horizontally. What's more, you can nest the VStack
and HStack
views together to create more complex arrangements.
Figure 3-17 shows one particular arrangement you can use to create your business card with the HStack
and VStack
views. It shows that the VStack
view is nested within an HStack
view. The VStack
view itself contains the series of Text
views, while the HStack
view contains the Image
view and the VStack
view.
The following statements implement the layout that you've just seen:
struct ContentView: View {
var body: some View {
HStack {
Image("weimenglee")
.resizable()
.frame(width: CGFloat(120),
height: CGFloat(120))
.cornerRadius(CGFloat(15),
antialiased: true)
VStack {
Text("Wei-Meng Lee")
.font(.largeTitle)
.bold()
Text("Founder")
Text("Developer Learning Solutions")
.italic()
Text("http://calendar.learn2develop.net")
Text("@weimenglee")
}
}
}
}
Figure 3-18 shows how the layout looks now.
bottom
center
firstTextBaseline
lastTextBaseline
top
The alignment
parameter specifies how views within the HStack
view should be aligned. What if you want the entire HStack
to be aligned to, say, the top of the screen? To do so, you can't rely on the alignment parameter in the HStack
view; instead, you need to call the frame()
modifier on the HStack
view and set its alignment as follows:
struct ContentView: View {
var body: some View {
HStack {
Image("weimenglee")
.resizable()
.frame(width: CGFloat(120),
height: CGFloat(120))
.cornerRadius(CGFloat(15),
antialiased: true)
VStack {
Text("Wei-Meng Lee")
.font(.largeTitle)
.bold()
Text("Founder")
Text("Developer Learning Solutions")
.italic()
Text("http://calendar.learn2develop.net")
Text("@weimenglee")
}
}.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .top)
}
}
The HStack
is now aligned to the top of the screen (see Figure 3-19).
The alignment
parameter of the frame()
modifier can assume one of the following values:
bottom
bottomLeading
bottomTrailing
center
leading
top
topLeading
topTrailing
To complete this example, you'll make the URL and the Twitter handle of the business card tappable. In other words, tapping the URL or the Twitter handle will launch the web browser on the iOS device and load the appropriate web page. To do so, you just need to wrap the URL and Twitter handle using the Button
view:
struct ContentView: View {
var body: some View {
HStack {
Image("weimenglee").resizable()
.frame(width: CGFloat(120),
height: CGFloat(120))
.cornerRadius(CGFloat(15),
antialiased: true)
VStack {
Text("Wei-Meng Lee")
.font(.largeTitle)
.bold()
Text("Founder")
Text("Developer Learning Solutions")
.italic()
Button(action: {
if let url = URL(string:
"http://calendar.learn2develop.net") {
UIApplication.shared.open(url)
}
}) {
Text("http://calendar.learn2develop.net")
}
Button(action: {
if let url = URL(string:
"https://twitter.com/weimenglee") {
UIApplication.shared.open(url)
}
}) {
Text("@weimenglee")
}
}
}.frame(maxWidth: .infinity,
maxHeight: .infinity,
alignment: .center)
}
}
To test the buttons, run the application on the iPhone Simulator. Press ⌘ +R in Xcode to run the application. Figure 3-21 shows how the application looks (left), the Safari web browser displaying the URL (center), and the Twitter page (right).
3.144.127.232