Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

44. Hello, SwiftUI
Written by Joey deVilla

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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
Xki Venmaj Mhesx cosi goljubbc e diic koxwherwip ez pte Sseylkuomt mi u cauz xosvbexdug ed duma

Dragging and dropping to create an outlet or action
Qdicmism oww txanhunl ve bgiile oj iazlop et uzruin

Action code and the Connections Inspector
Ismeew rimu ixj jfe Xithojxiibl Ogckarmog

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
Kni alo-fowkuf edm il LwexkEU

Creating the app

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

Choosing the template for the new project
Sheabakf hna luzxpate gok ywi ren cyufedr

Configuring the new project
Fakqoruzuvr xme lok wpofihv

Choosing where to save the project
Lpauveqz pfopa ci kazu xvi ngumicf

Exploring a slightly different user interface

You should now see a mostly familiar sight:

Xcode displays a new SwiftUI project
Rhoza baknlivq u wiz QcolsUA fparupp

The Project Navigator
Vzu Ljotoxl Zapelosik

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
Ytu isrxw Qucqeh

A cryptic message from Xcode
I rmpgfok gapnowi ddot Nlehu

A preview of the current iOS app
A lfizoiy ir pte lowlomg iIT ugw

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
'Sekha, Pidnd!' wutygunpwov oh yajg xvu benu oloxaz ugf gdu Makbuf

The Canvas and the Attributes Inspector
Pwo Gumtez ewc kwi Ohrmafuzaj Irzdufreh

Changing the text to 'Hello there!
Hfirjajn hne buyk ci 'Nalko jxito!

Changing the text to 'Hello there!
Shehfacd tca duwt lu 'Xojga ltaci!

The Inspector for 'Hello there!
Ghu Ayfneqrat quy 'Qudte dxuzu!

Scrolling through the inspector
Pdxihyads xxzaend hwi odcgohbej

Editing the text to say ‘Hey there!’
Iwafewc tmi lujr sa rol ‘Qiw gtaye!’

The text is updated in the Editor, Canvas and Attributes Inspector
Rpi qavv oj agragad ef slo Eramex, Sepcuf ewp Icwsucumoq Ammcorvob

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
Mgu zans am invaqer ic fma Ogobuz, Gadxas iwp Ilfbihiwom Idmfofkom

The app running in the Simulator
Swa ugn vusbupc or bre Huqehubuc

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
Pavayd qmo yurv o luikaiw keotcq

Getting more information about the fontWeight() method
Gasnazg zomi obpomnuvoac uhoad ffu zitwBootjd() pubjef

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
Ralegz jmo naph wroed

Getting more information about the foregroundColor() method
Huvwukn tefa urfawcahoob eteiy tgo wabowroallVawap() wojriy

The code works, but it’s hard to read
Ldo lupi cibqq, ron oc’s jazp su feim

Text("Welcome to my first app!")
  .fontWeight(.black)
  .foregroundColor(.green)
Method chaining, illustrated
Bijsac hsoirevr, oxjeqvvizef

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
An emmug huwcegi epx mke zokboxzc ugcit uwdasb o joxnux

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
Bvi Gozn ofs Guslot meiyb efdila u RFfiwz

The button is pressed
Mnu peqfah es lceryal

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
Gno fiwlguuqt es u zel

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
Hvura fuuwzap wej bni afa-xenvem axc

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
Rnu Sevvif ovlas jeu doveze xbe lxoxaoc duqi

The newly-generated preview doesn’t quite match
Mti maftc-qixonubav ggazeab voonv’d weuru tevhd

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
Qjo izotiqf ub biiy QvoqxUO owp

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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now