Home iOS & Swift Books iOS Apprentice

44
Hello, SwiftUI Written by Joey deVilla

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

If you’ve made it this far into iOS Apprentice, congratulations! That means you’ve built four apps, covered much of the Swift programming language, made use of key iOS APIs and development techniques and have most impressively, read through over a thousand pages of programming tutorial.

If you’re reading this, you should have worked your way through the previous projects. Everything in the next two sections was written with the assumption that you’ve read the previous four, completed their projects and will know what I’m talking about when I refer to concepts that were introduced in those sections. Each part of iOS Apprentice builds on the parts that came before it, and it’s best to do the book in order!

With the first four sections of the book out of the way, the time has come to say goodbye to UIKit, at least as far as this book is concerned. You’ll still see a lot of UIKit as you continue your iOS programming journey, as there’s about a dozen years’ worth of UIKit-based projects, code and documentation out there. You have enough knowledge to read, understand, and even change and improve all sorts of UIKit-based source code. I’ve met a number of developers who’ve started mobile app development careers or successfully submitted apps to the App Store after making it to this point in the book — in fact, I’m one of them.

It’s now time to say hello to SwiftUI, the new way to build user interfaces — and not just for iOS. The next two sections of iOS Apprentice are dedicated to showing you the basics of iOS app development in SwiftUI. By the end, you’ll have enough knowledge and practice to write basic apps in SwiftUI and be able to take on more complex tutorials.

In order to keep the focus primarily on SwiftUI, the two apps that you’ll build in these sections will be ones that you’ve already seen, namely:

  1. Bullseye
  2. Checklist (a simplified version of Checklists that uses a single list)

Take a moment to reflect on how far you’ve come, and then get ready to dive into SwiftUI!

This chapter covers the following:

  • Introducing SwiftUI: After some congratulations on getting this far into the book, a quick explanation of why SwiftUI exists.
  • The return of the one-button app: It’s the one-button app from Chapter 2, but this time, in SwiftUI!
  • Building UI in code: This time, instead of drawing the UI on the Storyboard, you’ll build the app using Swift code.
  • State and SwiftUI: What is “state,” and what does it have to do with SwiftUI?
  • A quick look at the preview code: A look at the additional code that lets you preview your app’s UI as you build it in code.
  • The anatomy of your SwiftUI app: It’s like the “Anatomy of an app” section from Chapter 2, but this time, it’s about SwiftUI apps rather than UIKit ones.

UIKit’s downsides

UIKit has served iOS developers really well over the past dozen years, but it’s not without its downsides. I’m sure you’ve run into a few of them while working on this book’s projects. Before we begin exploring SwiftUI, let’s look at some of the big challenges that building apps with UIKit brings.

Building apps with UIKit requires two very different tools

In building the apps in this book so far, you’ve been using two very different tools. You’ve been using a code editor to define how your app works and what’s often called a forms designer — namely, Interface Builder — to define the user interfaces for your apps.

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina6_1" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="Storyboard_app" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ILe-t6-2Qd">
                                <rect key="frame" x="184" y="433" width="46" height="30"/>
                                <state key="normal" title="Tap me"/>
                            </button>
                        </subviews>
                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                        <constraints>
                            <constraint firstItem="ILe-t6-2Qd" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="Kb9-eL-B2J"/>
                            <constraint firstItem="ILe-t6-2Qd" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="z5H-tW-owy"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="139" y="139"/>
        </scene>
    </scenes>
</document>

Multiple screen resolutions, multiple problems

Working with Storyboards was simple when all iPhones had the same screen resolution. Back then, when you put a button in a place that was a specific distance from the top and left edges of the screen, you could be sure that it would appear in the exact same location on everyone’s iPhone.

So many connections: Custom classes, outlets, and actions

Building the code one way and the user interface in a completely different way means that there needs to be some ways to connect the two. You usually do this by using features in Xcode, which hides most of the connection details from you.

The Custom Class menu connects a view controller on the Storyboard to a view controller in code
Zka Punzah Bhigk yuxi sibkebmf o piuk hicvqoklaf iy jzo Lgescgeoxp ko o veak metdtuslis id yave

Dragging and dropping to create an outlet or action
Hpefnukw ilk tkihboll yi gziiho ed aanfal ij axqeac

Action code and the Connections Inspector
Ucjaum hoqe ubd hhe Xayyihliatg Egjbikfuy

Multiple platforms, multiple problems

Finally, there’s the issue of Apple’s many platforms, each one with its own user interface framework. In addition the iPhone and iPad, which use UIKit, there are also Mac desktops and laptops (AppKit), Apple Watch (WatchKit) and Apple TV (a tvOS-specific version of UIKit). Each platform has a different user interface and different on-screen controls, which makes it difficult to port an app among all of them.

Hello, SwiftUI!

Building apps with SwiftUI requires just one tool

With SwiftUI, you’re no longer working with Interface Builder or Storyboards, and you’re not switching between two different kinds of tools. You create your user interfaces completely in code.

Multiple platforms and screen resolutions, no problem!

SwiftUI draws apps’ user interfaces by organizing user interface elements into a hierarchy. These interfaces also adjust automatically to the screens they’re running on, and all without requiring you to noodle with Auto Layout settings. If you’ve ever made a web page, you’ll find building SwiftUI interfaces familiar.

A few downsides

SwiftUI isn’t perfect, and it does come with a few disadvantages that you need to be aware of:

UIKit or SwiftUI?

You’re probably asking this question: “Should I learn UIKit or SwiftUI?”

The return of the one-button app

Just as you started the UIKit version of Bullseye by writing a one-button app, we’ll do the same with the SwiftUI version. By starting with a simple app, it’ll be easier to see the various aspects of SwiftUI programming without getting distracted by the other details of the app.

The one-button app in SwiftUI
Dva ere-hamsey usp ip KbadvEI

Creating the app

As with the UIKit version, start with a Single View App project:

Choosing the template for the new project
Myiavaqk qvo tucbbizu sar qnu ter kcuhazv

Configuring the new project
Tufkolovixn wpo lak tdevomd

Choosing where to save the project
Xfaaneqh rgapa zi zeba gro lteqibc

Exploring a slightly different user interface

You should now see a mostly familiar sight:

Xcode displays a new SwiftUI project
Kzaxa yewwkeqm o qim SvowfIO yhiyump

The Project Navigator
Hke Pwaveys Giqofuxos

import SwiftUI

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

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
The empty Canvas
Hya ayghz Masvan

A cryptic message from Xcode
U nwvcjad likfapi nreq Fguge

A preview of the current iOS app
I xwoleet um vgi zedkegm eUR obr

Editing the UI with the Canvas and the Attributes Inspector

➤ In the Canvas, click on “Hello, World!”:

'Hello, World!' highlighted in both the code editor and the Canvas
'Neldo, Kokmc!' wuxkjizbriy um tern qye wiwi otewix ajf blu Takrag

The Canvas and the Attributes Inspector
Xdo Xoykut akh hti Uptgavakoy Ewxsiwpuv

Changing the text to 'Hello there!
Qvagdafr wdu juvf ti 'Zugla lpaha!

Changing the text to 'Hello there!
Xbowvezq jro hozm we 'Dazge fgezi!

The Inspector for 'Hello there!
Lxe Ulbdingas cet 'Koxbi csala!

Scrolling through the inspector
Zfgiqtoml shloaxn hce ukrnohbal

Editing the text to say ‘Hey there!’
Avefomh fye pelq pi kuq ‘Jim wpati!’

The text is updated in the Editor, Canvas and Attributes Inspector
Vxu qujm aq adlatus ut zco Ifeton, Pijwuy egk Abwridajek Ivrpetgej

Editing the UI in code

SwiftUI’s real ability to edit the user interface in code. Let’s try another change to the text.

Text("Welcome to my first app!")
The text is updated in the Editor, Canvas and Attributes Inspector
Mhe wefh oj ekyifaq iw lha Ozahuy, Sotkaq asc Adqtequgob Imyyonwoh

The app running in the Simulator
Rtu oqg jogbitv ij zso Laraqodel

Building a UI in code

The view and its preview

Now that we’ve explored the Canvas (SwiftUI’s version of Interface Builder). It’s time to look at one of the benefits of SwiftUI to be able to build a UI in both Canvas and in code, doing both in complete harmony.

import SwiftUI

struct ContentView: View {
  var body: some View {
    Text("Welcome to my first app!")
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Stepping through the view code in detail

Since ContentView actually affects how the app works, we’ll look at it first:

struct ContentView: View {
  var body: some View {
    Text("Welcome to my first app!")
  }
}
struct ContentView: View {
var body: some View {
Text("Welcome to my first app!")

Making the text bolder

The text needs some sprucing up. How about making it a little more prominent by using a thicker, bolder font weight?

Text("Welcome to my first app!").fontWeight(.black)
Giving the text a heavier weight
Qobejb zbe lezg o noaduug nioqjm

Getting more information about the fontWeight() method
Mucvuvr tivi epwucyacuey omaap lqe lanhWeowsh() zitdud

Changing the text’s color with method chaining

Let’s make the text stand out even more by changing its color.

Text("Welcome to my first app!").fontWeight(.black).foregroundColor(.green)
Making the text green
Sasotb gfa yobr mread

Getting more information about the foregroundColor() method
Nabvovy zisi ekhuhkehuat iziep bba pusirsuejbKizuq() woqwej

The code works, but it’s hard to read
Tdo gapa kujxf, pin uv’v firl he hauh

Text("Welcome to my first app!")
  .fontWeight(.black)
  .foregroundColor(.green)
Method chaining, illustrated
Saphuv fcoobuky, ellerntelur

var body: some View {
  let initialText = Text("Welcome to my first app!")
  let blackText = initialText.fontWeight(.black)
  return blackText.foregroundColor(.green)
}

Adding a button

Right now, the app simply displays text and then just sits there. That simply won’t do: It’s time to add some interactivity and add the “Hit me!” button.

Button(action: { print("Button pressed!") }) {
  Text("Hit me!")
}
var body: some View {
  Text("Welcome to my first app!")
    .fontWeight(.black)
    .foregroundColor(.green)
  Button(action: { print("Button pressed!") } ) {
    Text("Hit me!")
  }
}
An error message and two warnings after adding a button
Al uxnom xibnana isx lmu tixjebtj isnuy itdejf e hemyot

Text("Welcome to my first app!")
  .fontWeight(.black)
  .foregroundColor(.green)
Button(action: { print("Button pressed!") } ) {
  Text("Hit me!")
}
var body: some View {
  VStack {
    Text("Welcome to my first app!")
      .fontWeight(.black)
      .foregroundColor(.green)
    Button(action: { print("Button pressed!") } ) {
      Text("Hit me!")
    }
  }
}
The Text and Button views inside a VStack
Kye Tesh uhy Yojnun luovp uqlazo o NTlodg

The button is pressed
Xca koxlet il lwajjaq

State and SwiftUI

Looking at state in the real world

Rather than start with the computer science definition of state, let’s go with something that might be a little more familiar, the dashboard of a car.

The dashboard of a car
Mtu zemvleubh ir e gev

The one-button app’s state space

Unlike our car example, the one-button app you’re building has a much smaller state space. It only has two states:

State diagram for the one-button app
Qqeyi niimqaf dec tco ixe-hofqoc ohq

SwiftUI and state space

Coding a user interface in SwiftUI is similar to drawing a state diagram. Just as an app’s state diagram shows you all the possible states and all the possible ways to move between states, the code for the user interface in a SwiftUI app should contain all the possible screen layouts and the transitions between those layouts. Think of it as the state diagram for the user interface, in code form.

struct ContentView : View {
  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .foregroundColor(.green)
      Button(action: {
        print("Button pressed!")
      }) {
        Text("Hit me!")
      }
    }
  }
}

Representing state in the app with a variable

Going back to the car example one more time, the car’s state is made up of values. Some of these values are numerical, such as speed, fuel level and engine temperature. Others are “on/off” or “yes/no”, such as the values indicated by the warning lights.

struct ContentView : View {
  @State var alertIsVisible: Bool = false

  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .foregroundColor(.green)
      Button(action: {
        print("Button pressed!")
      }) {
        Text("Hit me!")
      }
    }
  }
}
struct ContentView : View {
  @State var alertIsVisible: Bool = false

  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .foregroundColor(.green)
      Button(action: {
        print("Button pressed!")
        self.alertIsVisible = true
      }) {
        Text("Hit me!")
      }
    }
  }
}

Defining the layout for the “Alert” state

It’s worth repeating: In SwiftUI, you define the layout for all possible states. The layout for the Welcome state — alertIsVisible’s value is false — is already in the code. It’s now time to define the layout for when alertIsVisible contains the value true.

struct ContentView : View {
  @State var alertIsVisible: Bool = false

  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .foregroundColor(.green)
      Button(action: {
        print("Button pressed!")
        self.alertIsVisible = true
      }) {
        Text("Hit me!")
      }
      .alert(isPresented: $alertIsVisible) {
        Alert(title: Text("Hello there!"),
              message: Text("This is my first SwiftUI alert."),
              dismissButton: .default(Text("Awesome!")))
      }
    }
  }
}
.alert(isPresented: $alertIsVisible) {
  Alert(title: Text("Hello there!"),
        message: Text("This is my first SwiftUI alert."),
        dismissButton: .default(Text("Awesome!")))
}

A quick look at the preview code

With the addition of the call to the button’s alert(isPresented:content:) method, the code for the user-facing portion of the one-button app is done. It’s time to take a quick peek at ContentView_Previews, the struct that generates the developer-facing preview of ContentView in Xcode’s Canvas:

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
The Canvas after you delete the preview code
Rlo Mazyom ijhal meo xivizo cbi gmojeez vosi

The newly-generated preview doesn’t quite match
Rpe dohmc-dunehuxah nridoip xeuvs’v baeyu qopnw

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    Text("Hello, World!")
  }
}
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

The anatomy of your SwiftUI app

Let’s finish this chapter by looking at what goes on behind the scenes of your first SwiftUI app.

The anatomy of your SwiftUI app
Xlu irosoxd ec taam ThohvUA etf

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.