Learn how to build iOS apps
Get started with iOS 14 and Swift 5
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
Let’s build a fun iOS game with Swift and Xcode! Fire up your Xcode, get your Swift hat on and get hacking with this fun game called Add 1. BOOYAH!
In this tutorial, you’ll learn how to create a game for iOS with Swift. We’ll dive into Xcode, and you’ll learn how to work with variables, optionals, Interface Builder, Auto Layout, outlets, timers, and much more. It’s everything you need to get started with iOS development!
Let’s get started. Here’s a quick overview of this tutorial’s chapters:
Updated for Xcode 11.4, iOS 13 and Swift 5.2.
The iOS game we’e going to create is called “Add 1”. It’s inspired by Daniel Kahneman, a renowned psychologist and economist, and author of Thinking, Fast And Slow. Kahneman used a game, that lets people “add 1” to an arbitrary sequence of numbers, to test a concept called cognitive strain.
The goal of the game we’re building is to “add one” to each of the numbers of a 4-digit sequence, as many times as you can in 60 seconds. You get one point for each correct answer. How many points can you score?
It’s a simple and fun iOS game project – perfect for a Thursday night or Sunday afternoon of playing with Swift. By the end of this tutorial, you’ll know how to make a simple game app for iOS and you’ll be able to challenge your friends, to see who can make the most “add ones” in one minute.
You don’t need much to get started with this iOS game tutorial, but you need at least:
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
You’ll need to download the following assets to complete the app project:
.zip
file, which contains an .otf
file.zip
file, which contains a bunch of image filesThis iOS app project makes use of the following free assets:
Let’s get started!
The first step to build the Add 1 app project, is to set up Xcode. Here’s how:
Next up, we’re going to change some of the project’s settings.
These changes are minimal, but essential. Setting the device orientation to Portrait means that you won’t be able to rotate the game’s User Interface (UI), which is exactly what we need. And hiding the status bar is just a nicety, so it won’t interfere with the game UI. Neat!
Want to get a tour of Xcode? Check out this tutorial: Xcode 11 Tutorial for Beginners
When you set up your Xcode project, Xcode automatically added a view controller and Storyboard to the project.
A view controller is a common component in iOS apps. It controls the way a User Interface (UI) looks, and how a user interacts with it. A view controller typically consists of a Swift class and a Storyboard (or XIB). The Storyboard contains layout and UI information, and the Swift class contains the code that governs the view controller.
The project’s default view controller is just named ViewController
. We’re now going to rename the view controller to GameViewController
, because that better describes what the view controller does.
Here’s how:
ViewController.swift
file, by clicking on it in the Project Navigator
class ViewController: UIViewController {
Refactor -> Rename...
from the menuGameViewController
, and then click Rename in the top-right cornerWith the refactoring tool, we’ve now renamed the ViewController
class to GameViewController
. The tool also renames references to ViewController
in the Storyboard, and the Swift filename itself. That’s super useful, because it saves us from having to manually change these references!
Refactoring is the practice of rewriting programming code to make it better structured, easier to extend, more concise, and easier to read. It’s important in practical iOS development, because improves how your app works “under the hood.”
Before we can build the app’s User Interface (UI) in Interface Builder, we’ll need to do some setting up, such as importing graphics assets and fonts.
The game UI consists of an input field, a few text labels, and a bunch of images. Building your app’s UI is an important part of the app development workflow. You build UIs with Interface Builder (or SwiftUI). Interface Builder is a UI editor that’s built into Xcode.
Here’s a quick overview:
You can see Interface Builder if you open Main.storyboard
in Xcode, by clicking on it in the Project Navigator.
Interface Builder is a scaffolding tool. You don’t use it to create interactive UIs, but it’s used to set up the UIs you need for your app. A XIB or Storyboard, the file types of Interface Builder files, merely contain information about the layout and views of a UI and their attributes.
A view controller contains a main view, which consists of subviews. Subviews can be text labels, buttons, image views, input fields, table views, and so on. Views have attributes, such as a background color.
For this game app we’re working with a Storyboard. A Storyboard is an Interface Builder file that contains multiple UIs, and some settings that govern the flow and transitions between these UIs. The game app we’re building uses a Storyboard that contains just one UI.
Quick Tip: In Xcode, you can add new User Interface elements to your Storyboard or XIB by using the Library button, in the top-right corner of Interface Builder.
+
icon.After clicking it, a gizmo that shows all available UI elements pops out. Use the search field to find the UI element you need, and then drag-and-drop it onto the editor canvas.
Here’s what the complete User Interface looks like in Interface Builder:
The User Interface of the Add 1 app has 9 different components:
UIImageView
. This image shows a blue/green full screen background graphic.UIImageView
. This image is displayed behind the Score Label – it’s a graphic UI element for the user’s score.UILabel
. This label displays the user’s score as text. We’ll update it dynamically if the user scores a point in the game.UIImageView
. Again, this is a graphic for the countdown timer.UILabel
. This label displays the user’s remaining game time, and it will count down to zero.UIImageView
. This is another UI graphic that puts a nice border around the Number Label.UILabel
. This label displays the random 4-digit code that’s at the center of the Add 1 game.UITextField
. It’s used to input an answer to the game’s Add 1 challenge. We’ll also give this text field a nice background image.UILabel
. This label will read: “Add 1 to each of the digits. So, 1357 becomes 2468”, to explain the game to the user.We’re going to use 3 different colors. Their RGB color codes are:
255, 255, 255
or #FFFFFF
135, 79, 33
or #874F21
105, 168, 255
or #69A8FF
You need a few graphics assets, and a custom font called HVD Comic Serif Pro, to build the game’s User Interface. Here’s how you can get them:
.zip
files in a convenient locationFirst, we’re going to add these image files to Xcode. Here’s how:
Assets.xcassets
library file in Xcode, by clicking on it in the Project Navigator
AppIcon
)Then, the next step is to add the custom font to Xcode. Adding custom fonts in your app is a bit tricky. In short, the steps we need to take are:
.otf
font file to XcodeInfo.plist
First, make sure that the HVD Comic Serif Pro font is installed on your Mac. You can do this by double clicking the HVD_Comic_Serif_Pro.otf
file in Finder. In the dialog that appears, click Install Font.
Then, with the font’s folder open in Finder, drag-and-drop the .otf
font file from Finder into Xcode. Make sure to drop it onto the Project Navigator!
In the dialog that appears, select the checkbox before Add to Target. This will add the font file to the app project, as an asset.
Then, open the Info.plist
file in Xcode by clicking on it in the Project Navigator. This is a property list file, and it includes basic information about our app project.
We’re going to add the filename HVD_Comic_Serif_Pro.otf
to the Fonts provided by application field in Info.plist
. Here’s how:
HVD_Comic_Serif_Pro.otf
, the filename of the font.The font is now added to Xcode! Later on in this tutorial, we’ll use the font to build the game’s User Interface.
Working with a different font, or does your font have a different filename? Just complete the above steps, but use the different filename.
Alright, let’s get a move on! It’s time to build that game UI… Make sure to open Main.storyboard
in Xcode first.
Before you continue, we’ll need to adjust a setting in the Storyboard. Here’s how:
This setting will enable us to create a full screen layout. You use the Safe Area Layout Guides to constrain the UI within an iPhone’s top and bottom edge. This is especially important on iPhone X, iPhone 11 Pro, etc., because those edges can prevent a user from seeing the complete UI. We don’t have to take that into account, because we’re going to create a full screen UI.
Next, we’re going to start with the Background Image.
1. Background Image
First, open the Library by clicking on its button in the top-right corner of Interface Builder. The button looks like a +
plus sign. Find the UIImageView
in the list – you can search for image view – and then drag-and-drop it from the list onto the editor’s canvas.
Then, resize the image view so it takes up the entire view controller, including the status bar. It’s OK to position the image view behind the iPhone’s top notch.
Next, we’ll have to change the image view’s settings. Here’s what you do:
background
. This is the background image you imported earlier.Neat! The Content Mode setting ensures that the image inside the image view resizes to take up as much space as it can, effectively “filling” the image view. It’s super helpful for differently sized screens, such as the iPhone 11 or iPhone 8 Plus.
The names of image assets, as found in the Image attribute, are exactly the same as the asset names you find in Assets.xcassets
. Good to know!
2. Score Image
Next up is the image that’s shown behind the score label. It’s a simple graphic. Here’s how you add it:
score
120 x 50
pointsx
and y
coordinates are set to something like 20
and 50
.As you’re dragging a UI element over the canvas, blue helper lines appear. These guides can help you position the UI more easily. Along the edges of the screen, they take into account a safe margin for viewing.
3. Score Label
We’re now going to place a label on top of the score image. Here’s what you do:
UILabel
) in the Library and drag-and-drop it onto the canvas1234
– you can do this by double-clicking on the label itselfWe’re now going to change the font of the label. Here’s how you do that:
T
button to select a different font22
Now that you’ve changed the font size, you may need to reposition the label once more, so it’s centered nicely on top of the score image view.
Quick Note: If you can’t select find the right font in the dropdown, you’ve probably missed a step in the previous section, when adding the custom font to Xcode.
4. Time Image
The time image and label are exactly the same as the score image and label, except that they’re at the top-right of the view.
First, let’s add the time image. Here’s how:
time
.150 x 50
points.x = 244
and y = 50
.The position coordinate system in Interface Builder, and on iOS, works in such a way that the top-left corner of a view has X, Y coordinate (0, 0)
. A view’s origin point is also its top-left corner.
5. Time Label
Adding the new time label is exactly the same as adding that previous score label. Here’s what you do:
00:00
22
6. Number Image
The number image is the background for the number label. We’re going to add it to the top-center of the view. Inside the label (see below), the game shows the generated numbers for the Add 1 game.
Here’s how you add the number image:
number
, in the Attributes inspector
300 x 130
points, in the Size inspector
7. Number Label
The number label is centered within the number image. The label itself shows a 4-digit number that’s randomly generated.
Here’s how you add the number label:
1234
135, 79, 33
, see below)70
It’s easiest to change the color like this:
135, 79, 33
If you don’t see input fields for RGB, make sure you’ve opened the Color Sliders tab, and then select the RGB Slider from the dropdown.
On iOS, one way to work with different colors is by using RGB, which stands for Red, Green and Blue. We can either express RGB colors in 3 values between 0 and 255, or by using a hexadecimal code like 874F21
. This hex code uses 3 pairs of 2 values between 0
and F
. Every pair corresponds to a value on the 0
to 255
scale, which adds up to an RGB code again.
8. Input Text Field
The input for the game is a text field (class UITextField
), that the user uses to input a 4-digit number. When the text field gets selected, the on-screen keyboard pops up. And unlike the other UI elements, this input UI has an actual background image itself!
Here’s how you add the text field to the UI:
105, 168, 255
, hex: 69A8FF
)70
input
256 x 110
points.Position your views in such a way that the text field isn’t (partially) hidden by the on-screen keyboard. If they’re both above the midline across the screen, you’re good!
9. Explanation Label
And last but not least – the explanation label! This label briefly explains how the game works. And it’s nicely hidden from view when the on-screen keyboard pops up.
Here’s how to add it to the UI:
24
2
375 x 65
pointsAwesome! You’ve finished setting up the UI elements. Let’s continue with Auto Layout constraints…
If you “lose” the label off-screen when you change the label’s text, and you can’t resize it, simply set its width manually with the Size Inspector to get it back on screen.
Oh yes, Auto Layout. This is going to be fun! We’re now going to add some rules to the User Interface, to make it resize itself nicely on differently sized screens. These rules are called constraints, and the layout system they’re a part of is called Auto Layout.
With constraints, we can determine what happens when a User Interface changes size, or is shown on a differently sized iPhone. Based on these constraints, the Add 1 game app can be used on iPhone models with different screen sizes, such as the iPhone 8, 8 Plus, X or 11 Pro.
The constraints we’re about to add aren’t complex, but if you’re new to Auto Layout, working with constraints can surely seem challenging. Just take it slow, read carefully what to do, and you’ll have a working UI in no time.
In short, here’s what the constraints are going to do:
To add a constraint, first select the UI element you want to add the constraint to. Then, click the Add New Constraints button, or the Align button, in the bottom-right of the Interface Builder editor. The Add New Constraints button looks a bit like a Star Wars TIE Fighter. Like this:
Let’s get started!
1. Background Image
First, we’re going to add constraints to the background image. Here’s how:
0
The 4 red struts represent the Leading, Trailing, Top and Bottom edges of the image view. You can use them to determine the distance between one UI element, and the next.
Right now, we’ve set distance between the 4 edges of the image view and the edges of the screen to 0
. When the view resizes, the image view sticks to the edges of the screen, essentially creating a full screen background image.
A constraint, such as the 4 we created, connects two edges of two UI elements with each other, optionally adding some space between them. This can either be the edge of the next UI element, or the edge of the superview.
2. Score Image
Next up, the score image. We’re going to affix that to the top-left edge of the screen. We’ll also set its width and height with constraints.
Here’s how:
If you’ve looked carefully, you saw that the top and left constraint values were already set to the distance between the UI element and the edges of the screen.
In the screenshot below, the top constraint is set to 6
, and the left constraint to 20
. This corresponds to the x and y coordinates we set for the image view.
With the width and height constraints you can fix a UI element to a certain size, of course. You can also set either one of them, to allow a UI element to grow in size.
3. Score Label
OK, now let’s add constraints for the score label. We’re going to center this label on top of the score image. Here’s how:
These two constraints, who determine the alignment between 2 UI elements, will center the label on top of the image. Auto Layout will first determine the position for the image, and then center the label on top of it.
An important principle of Auto Layout constraints, is that the constraint resolver will attempt to satisfy all constraints. It doesn’t apply constraints in any given order, but instead will attempt to find a position and size for a UI element that fits all of its constraints. This is efficient, but sometimes challenging to comprehend, and it can lead to constraints breaking.
4. Time Image
Next up, the time image. We’re going to repeat what we did for the score label and image, and apply it to the time label and image.
Here’s how:
This will affix the time image to the top-right of the screen. Neat!
5. Time Label
Just like the score label, the time label is centered on top of the time image. Here’s how:
You may notice that the time label is slightly off center. Let’s fix that! We’re going to change the offset of the Center X Alignment constraint of the time label.
Doing so consists of 2 steps:
-20
You can select the constraint in no less than 3 ways! Here’s how:
00:00.centerX = time.centerX
constraint.Which approach you use, is up to you! With the right constraint selected, go to its Size Inspector. Finally, set the input field for Constant to -20
. The time label should now shift slightly to the left of the center of the time image. Nice!
Take a moment to study the Size Inspector for this constraint. If you look closely, you’ll see that a constraint is actually a “relation” between two items. To center the time label and time image onto each other, horizontally, we’re saying that both their centerX
properties are equal. We’ve also added an offset of -20
. Feel free to inspect other constraints too!
6. Number Image
OK, let’s move on to the number image. That’s the image behind the number label. We’re going to center it horizontally on screen, and position it below the score and time images.
Here’s how:
60
If you look carefully, you’ll see a blue line appear between the number image and the score image (left). This is the constraint that puts about 60
points of distance between those UI elements.
That’s the core principle of working with constraints: putting space between UI elements, to determine how their positions relate to each other.
Can’t keep horizontal and vertical centering apart?
7. Number Label
Moving on! Next up: the number label. You’ve guessed it – this one is centered on top of the number image, both horizontally and vertically.
Here’s what you do:
If you want, you can slightly adjust the number label inside the number image. Select the Center Y constraint by clicking the blue horizontal line across the image. You can set its constant to about 5
.
8. Input Text Field
Almost there! We’re now going to set the constraints on the input text field. That’s the one users use to input numbers for the Add 1 game. Here’s how:
20
250 x 110
Awesome!
9. Explanation Label
And, last but not least, the explanation label at the bottom of the UI. Do you really need step-by-step instructions for that one? Of course not!
Here’s what needs to happen:
65
50
375
If the above constraints causes problems on smaller screens, remove the width constraint, and add some space by setting the leading and trailing constraints to about 20
.
At this point, you should be able to change the device model you’re viewing this UI as, in Interface Builder. You can do so by clicking View as: at the bottom of Interface Builder. Then, select a different Device. The UI should now update and show you the different device, with different dimensions.
And if you run the app right now, by clicking Play or pressing Command + R, the game should show right up in iPhone Simulator. Awesome!
Before we can create the game with Swift code, we need a way to change the User Interface (UI) with code. For example, the score label needs to get updated with the user’s score, and the time label needs to show the remaining time.
All that happens with Swift code, but how do we connect that code to the UI in Interface Builder? That’s where outlets come in.
First, open the GameViewController.swift
in Xcode by clicking on it in the File Navigator on the left. You’ll see an empty Swift view controller. It looks a bit like this:
class GameViewController: UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
}
}
The GameViewController
is the class that will govern what happens in the Add 1 game, and specifically its User Interface. It’s the code equivalent of the UI we just created in Interface Builder.
Connecting a UI component, from Interface Builder, to code in the view controller class, takes 2 steps:
It’s as simple as creating a property in the class and connecting that to a UI component. The property will then hold a reference to the UI component, when the view controller loads.
First, add the following lines of code to the top of the GameViewController
class:
@IBOutlet weak var scoreLabel:UILabel?
@IBOutlet weak var timeLabel:UILabel?
@IBOutlet weak var numberLabel:UILabel?
@IBOutlet weak var inputField:UITextField?
Make sure to add them within the class, so inside the first opening squiggly bracket {
. Here’s what happens on those lines:
keyword means that the property we’re creating is an outlet. A property is a reference (a variable) that’s part of the class. weak var
. That means that this property is weak, as opposed to strong. Without going too deeply into memory management; using weak
as a default will potentially prevent a strong reference cycle later on.numberLabel
of type UILabel
.nil
, written with the question mark ?
at the end of the declaration.What’s most important here are the names of the properties and their types. You see we’ve created 4 properties, with the names scoreLabel
, timeLabel
, numberLabel
and inputField
. The labels have type UILabel
, and the input field has type UITextField
. All properties are optionals.
If you want to learn more about view controllers, outlets and properties, check out this tutorial: View Controllers Explained: Ultimate Guide For iOS & Swift.
The next step is connecting the UI elements to the outlet properties in Interface Builder. Here’s how:
Main.storyboard
in Interface BuilderNext up, we’re going to connect the outlet properties to their respective UI elements. First, make sure you’ve found the properties below Outlets. Do you see those small circles next to them? We’re going to draw lines from those circles to the UI elements in the editor.
Here’s how:
scoreLabel
property below Outlets
You should now see Score Label next to the scoreLabel
property. Make sure to repeat the steps for the other 3 outlets!
timeLabel
to the time label (the right one)numberLabel
to the number label (the middle brown one)inputField
to the input text field (the bottom blue one)You should end up with 4 outlet connections. Note that a default outlet connection, for view
, is already present. Here’s a helpful screenshot, that shows making the outlet connection:
Quick Tip: You can find a UI element’s type via the Identity Inspector. Select the UI element in Interface Builder, go to the Identity Inspector, and see what’s next to Class. It’s important that the type of an outlet property matches the type of the UI element.
We’re now going to write some code to generate a string of random numbers. This string, such as "9163"
, consists of 4 characters. It’s the basis of the Add 1 game, because a user needs to “add 1” to each of the number’s digits.
The code we’re about to write is an extension. We’re extending the String
type with a new function, called randomNumber(length:)
.
Here’s how:
String.swift
and save it alongside the other Swift files of the projectA new Swift file appears! We’re going to add some code to it. First, add the following statement to the file. You can add it right below import Foundation
.
extension String
{
}
The extension
block tells Swift we want to extend the String
type. Next, add the following code within the squiggly brackets {
and }
:
static func randomNumber(length: Int) -> String
{
}
The above code is called a function declaration. We’re declaring a new function called randomNumber(length:)
. It has one parameter length
of type Int
, and its return type is String
. Differently said, the function accepts an integer as input and produces a string as output.
The static
keyword is used to add this function to the class String
, rather than an instance of String
. This is often called a class method, as opposed to an instance method. At a later point, we’re going to call the function like this:
let number = String.randomNumber(length: 4)
Next up, we’re going to add the following block of code within the randomNumber(length:)
function:
var result = ""
for _ in 0..<length {
let digit = Int.random(in: 0...9)
result += "\(digit)"
}
return result
What’s going on? This is:
result
of type String
. It’s initialized with an empty string.length
. When length
is 4
, the loop will repeat 4 times.result
string.result
. This is the output string of the function: a string with random numbers, of a given length
.We now have a function to generate random numbers with. Neat!
What’s that "\(digit)"
bit? That’s called string interpolation. You can use it to include a variable in a string. In this case, we’re using it to convert the integer to a string, so it can be appended to result
.
We need a way to keep track of a user’s score. To that end, we’re going to add another property to the GameViewController
class.
First, switch over to the GameViewController.swift
file. Then, add the following property to the top of the class, below the outlet properties:
var score = 0
Easy, right? But what is the type of the score
property? In the above code, the 0
is an integer literal of type Int
. Based on that, we can infer that score
has type Int
. In fact, that’s exactly what Swift figures out – with type inference – if we don’t provide a type annotation in our code.
OK, we’re going to code 2 functions next. These functions will help us manage the game, later on.
First, add this function to the class:
func updateScoreLabel() {
scoreLabel?.text = String(score)
}
What’s the function do? It merely updates the score label, with the current score. We’re using the text
property of the scoreLabel
property, and set it to "\(score)"
. Two things stand out here, though!
?
right after scoreLabel
is used for optional chaining. When scoreLabel
is nil
, the code graciously and silently stops at that point, and continues to the next line. The effect? We’re unwrapping the optional, and only continue when it’s not nil
.String(···)
code is called an initializer. We’re using the score
property as the parameter for this string, effectively converting the integer to a string. After all, the type of the text
property is String
.OK, now add the following function to the class:
func updateNumberLabel() {
numberLabel?.text = String.randomNumber(length: 4)
}
What’s going on in there? Here’s what:
numberLabel
is set to the result of the randomNumber(length:)
function. In other words, we’re generating a random number and assign it to the number label.randomNumber(length:)
function? That’s coming from our extension of the String
type! Now that it’s added to the type, we can use it everywhere. Neat!Quick question: Why are we adding such simple functions to the class? They’re just one line of code long!
Well, what we’re doing right here is creating abstractions. We’re abstracting away some block of code, so we can reuse it later. Whenever we need to update the score or number labels, we can just call that function. We don’t have to bother with the text
properties, or optional chaining.
And if we use the functions more than once, we’ve also saved some lines of code. You don’t have to maintain or debug the code you don’t write!
The viewDidLoad()
function is the starting point of any view controller. At that point, the view is loaded but not yet shown on screen. It’s called once in the lifetime of a view controller, which makes it the perfect setup point for a view controller.
At the start of the Add 1 game app, the score needs to be set to zero, and we also want to show the first random number to the user. Guess what? We just coded the perfect functions for that purpose.
Add the calls to the updateScoreLabel()
and updateNumberLabel()
functions to the viewDidLoad()
function. Your function should now look like this:
override func viewDidLoad()
{
super.viewDidLoad()
updateScoreLabel()
updateNumberLabel()
}
What does override
mean? The viewDidLoad()
function is defined in UIViewController
. Every view controller subclasses that UIViewController
class, and inherits its functionality. When we want to customize some of its functionality, we can override the function and customize its implementation.
The super.viewDidLoad()
function call gives us access to the parent of the GameViewController
class instance, which is UIViewController
. In other words, we’re calling the super implementation of viewDidLoad()
. We’re overriding the function, but still call its “super” implementation prior to overwriting it.
Quick Question: Can you assert what the text inside the score label and number label is, at the start of the game?
We’re ready to code the most important part of the Add 1 game app: validating a user’s input, and increasing the game score! In short, we’ll need to respond to user input in the text field, check if the input is OK, and increase the score.
Here’s what we’re going to do:
Let’s get started!
The first step is to respond when the user has edited the text of the input field. We’re going to call a function when the inputField
emits an .editingChanged
event.
First, make sure to add the following function to the GameViewController
class:
@IBAction func inputFieldDidChange()
{
}
This function is going to be called when the user edits the text in the input field. We’ve designated the function with , which means that we can connect this function to an action in Interface Builder. This is similar to an outlet.
Here’s how you can make that connection:
Main.storyboard
in Interface Builder and select the input fieldinputFieldDidChange()
functionYou can also connect actions to UI elements with code. This mechanism is called target-action. The steps we’ve just taken in Interface Builder, can also be coded like this:
inputField?.addTarget(self, action: #selector(inputFieldDidChange), for: .editingChanged)
You don’t have to add this to your code, but if you choose to do so, make sure to remove the Interface Builder-based action first.
Next up, we want to grab hold of the text inside the input field. We’ll also get the text in the number label, so we can compare both.
First, add the following code inside the inputFieldDidChange()
function:
guard let numberText = numberLabel?.text, let inputText = inputField?.text else {
return
}
In the above code, we’re combining the guard statement with optional binding. What you get, is a guard let
block that evaluates whether an expression is nil
. When it isn’t nil
, the non-optional values are assigned to their respective constants. When a guard let
expression is nil
, the else
block is invoked, which will return and exit the function.
In other words, 2 things can happen with the above code:
numberLabel?.text
or inputField?.text
is nil
, and the function returns and exitsnil
, and they get assigned to numberText
and inputText
, and the function continuesWith guard let
, the constant you defined can be used throughout the function scope. We can use numberText
and inputText
constants in the rest of the function. This is counter-intuitive compared to if let
, whose constants are only available inside the conditional. However, guard
exits the function, and scope, which (hopefully) makes up for the confusion…
The inputFieldDidChange()
function will be called for every change in the input field, which means for every character the user types in there. We only want to respond to complete input, i.e. the 4 digits that the user enters as the answer to the Add 1 challenge. In other words, we only need to check a result if it’s 4 characters long.
Here’s how. Add the following code to the inputFieldDidChange()
function, below the previous code.
guard inputText.count == 4 else {
return
}
That’s reasonably simple, right? We’re using the guard statement again to “guard that inputText.count
equals 4, or else exit“. The code will return and exit the function when the number of characters in the inputText
string is not 4.
By the way, we could have used an if
statement here, but a guard
statement is often more descriptive. Here, check out these two:
// Option 1: With guard...
guard inputText.count == 4 else {
return
}
// Option 2: With if...
if inputText.count != 4 {
return
}
Both have the same effect, but the “guard that, or else” is much more descriptive. It’s intuitive, even.
Moreover, the guard
statement forces you to “transfer control out of scope”, or differently said, to exit the function. A mere conditional doesn’t have this rule, which makes using guard
clearer, and more explicit. You can’t accidentally alter the flow of the code, without also changing the guard
statement.
Here’s what we got so far:
In the Add 1 game app, a user needs to “+1” to each of the digits in a number string. We now have a handle on both the number string, and the input string. Both have type String
.
If we want to calculate whether the input string is correct, we’ll have to compare each of the digits from the number string agains the input string. If each of the digits is equal to the other, plus one, we have a correct result.
To that end, we need a way to access the individual digits in the number strings. In many programming languages, you can just treat the string as an array of characters, and get, for example, the third character like this: inputText[2]
. We’d be able to check whether a pair of digits is correct with something like this:
if inputText[n] == numberText[n] + 1 {
// This pair of digits is correct, it's "+1"
}
Unfortunately, dealing with strings in Swift is not that easy. For a number of reasons, most notably that strings have a variable-width encoding, you can’t randomly access any character in a given string in Swift, without some more work.
To make the GameViewController
code as simple and clear as possible, we’re going to add another function to our extension of String
. This function will accept an integer as input, and it’ll produce the number at that position in the string.
First, make sure you’ve opened String.swift
in Xcode, via the Project Navigator, and then add the following code to the String
extension:
func integer(at n: Int) -> Int
{
let index = self.index(self.startIndex, offsetBy: n)
return self[index].wholeNumberValue ?? 0
}
What’s going on here?
integer(at:)
accepts an integer as input, and produces an integer as output.self
in the function refers to the current string. We can call the function integer(at:)
on any String
value, after which the function will use that string we call it on.index(_:offsetBy:)
function to get an index of a position in the string. Swift will count from the beginning of the string to n
, and create an index for the position under n
.index
to get the character from the string. We use the wholeNumberValue
property of this character to get the integer number this character represents, such as integer 1
for character "1"
.0
.Pfew! That’s quite some complex code, for something so simple. There are 2 things you need to understand here:
wholeNumberValue
will always return an integer number. After all, we’re working with strings of numbers! When the user accidentally inputs a text character, like "a"
, we regard that as 0
, which is always wrong.Let’s move on!
Note: The integer(at:)
function is only meant for the Add 1 game app! Don’t add it to your own, other app project, unless you know how it works and what its consequences for your code are.
Finally, we’re getting to the hairy part of the Add 1 game app: checking if a user’s input is correct. How are we going to figure out if their +1’s are right?
Here’s the basic algorithm:
9
is compared to 0
, and not 10
.This approach makes sense, because the user only needs to make one mistake for the entire input to be incorrect. That’s why we’re starting out with a “correct” result, until we find otherwise.
First, add the following code below the last guard
statement:
var isCorrect = true
Simple, right? This variable is going to keep track of whether the result is correct. It’ll start out at true
.
Below that, add the following for loop:
for n in 0..<4
{
}
This loop iterates 4 times. For each of the iterations, we get access to a constant n
which contains the index of that iteration. Based on the 0..<4
range, we can assert that the loop runs over indices 0, 1, 2 and 3. We’ll use the index n
to grab the right digit from inputText
and numberText
.
Add the following code inside the for loop:
var input = inputText.integer(at: n)
let number = numberText.integer(at: n)
This code uses the String
extension we created earlier. Thanks to the integer(at:)
function, we can now get the integer number at a position in the strings. Internally, that function uses string indices and the wholeNumberValue
property to grab the integer from the string.
It’s important to keep track of variable and constant types in Swift. Can you figure out that, in the above code, input
has type Int
, and inputText
has type String
?
At this point we can compare the digits input
with number
, but there’s one special case we need to take in account. What happens when number
, i.e. a digit from the given number, is 9
?
Based on the principle of Add 1, the input
digit then needs to be 10
. However, you can’t type double digit numbers into the input field. On top of that, we expect the user to input 0
for 9
, in a circular fashion. How do we deal with that?
Add the following code below the previous code, inside the for loop:
if input == 0 {
input = 10
}
What’s going on here?
0
is the actual right answer for the given number digit 9
. 10
is the right answer for given number digit 9
, based on the Add 1 principle that input == digit + 1
is a correct answer. input
is 0
, we’re deliberately changing input
to 10
, so that 10 = 9 + 1
results in a correct digit, because 0
is the right answer for 9
!Moving on! Add the following code below the previous code, inside the for loop:
if input != number + 1 {
isCorrect = false
break
}
What does this code do? It’s an if
statement that checks if input
is not equal to number + 1
. When it’s not equal, we know that the entire input is incorrect. And so we set isCorrect
to false
.
The break
statement is a small optimization that’ll stop the for loop. There’s no need to continue with the next digits, because the complete answer is already incorrect.
Why are we checking whether input
is wrong, instead of right? Remember that we started out with isCorrect = true
. We’re assuming that an answer is right, until we prove it’s not. We iterate over the digit pairs, and set isCorrect
to false
when we encounter an incorrect digit. When we don’t find any wrong digits at all, isCorrect
will remain true
.
Finally, add the following code below the for loop, so outside of it:
if isCorrect {
score += 1
} else {
score -= 1
}
updateNumberLabel()
updateScoreLabel()
inputField?.text = ""
The above block of code will first evaluathe the isCorrect
boolean. Like this:
isCorrect
is true
, we add 1 to the score
variable, with +=
isCorrect
is false
, we subtract 1 from the score
variable, with -=
Then, in the second part of the above code, we’re calling functions to update the number label with a new randomly generated string, and we update the score label with the new score. Finally, we’re emptying the input field, so the user can add a new answer in it.
The a += b
operation is the same as a = a + b
. The a -= b
operation is the same as a = a - b
. In the above code, we’re increasing or decreasing score
by 1. In Swift, the a++
and a--
operators don’t exist!
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
You can just keep playing the Add 1 game as it is right now, which is no fun. We need some boundaries! Who can score the most points in 60 seconds? The app needs a timer.
In Swift, you can use the Timer component to take some action at a given interval. We’re going to write some code that counts down from 60 to 0, by subtracting 1 every second. Let’s get started!
First, we’re going to add 2 new properties to the top of the GameViewController
class. Like this:
var timer:Timer?
var seconds = 60
Awesome. As you’ve guessed, the timer
property is going to hold on to a reference to the Timer
object, and we’ll use seconds
to keep track of the remaining time for the game. Note that the timer
property is an optional, so it can be nil
or contain a Timer
object.
Next up, we’re going to add a new function called updateTimeLabel()
. Add the following function to the GameViewController
class:
func updateTimeLabel() {
let min = (seconds / 60) % 60
let sec = seconds % 60
timeLabel?.text = String(format: "%02d", min) + ":" + String(format: "%02d", sec)
}
What’s up with that?
min
and sec
constants.String(format:)
initializer, we’re using the special string format %02d
to format the min
and sec
integer values as zero-padded strings. That way, 7
seconds is written as 07
, with a 0
in front.+
and assigned to the timeLabel
.Before you move on, quickly add a call to the updateTimeLabel()
function to the viewDidLoad()
function. That way, the time label is updated at the start of the game. Like this:
override func viewDidLoad()
{
super.viewDidLoad()
updateScoreLabel()
updateNumberLabel()
updateTimeLabel()
}
Then, last but not least, add the following code to the end of the inputFieldDidChange()
function. So, before the closing squiggly bracket }
, but after inputField?.text ···
.
if timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
if self.seconds <= 60 {
self.seconds -= 1
self.updateTimeLabel()
}
}
}
Let’s take a closer look at that bit of code. Here’s what’s going on:
Timer.scheduledTimer( ··· )
call creates and starts a timer. Its time interval is 1.0
second, and it will repeat (every second). The Timer
object that’s created, is assigned to the timer
property.scheduledTimer( ··· )
is a closure. It’s a block of code that is executed when the timer fires, so every second.seconds
property is less than or equal to 60
. When it is, we decrement score
by 1, and call updateTimeLabel()
. This will make the time label count down to zero.But, there’s more… On the first line, on the outermost level of the code, we’re checking if timer
equals nil
. This is on purpose. Keep in mind that the above code, because of where it’s written, is called when the user’s input is evaluated, in inputFieldDidChange()
.
When the app starts, timer
is nil
. The first time a user inputs an answer, and inputFieldDidChange()
is called, the above code block executes, because timer
is nil
. That means the timer will only start counting after the user has entered their first answer!
On subsequent answers, unless timer
is nil
again, nothing happens, because timer
isn’t nil
anymore. The timer will keep firing every second though, because the timer’s closure runs independently from the inputFieldDidChange()
code. Before you continue, make sure you understand how the code works!
The self
in the closure is a reference to the current instance of the GameViewController
. Closures that use properties from the class they’re created in, need to use self
to make explicit that they’re strongly referencing the current class. You can read more about that in this tutorial.
We’re almost ready! The last thing we’ll need to code, is what happens when the game finishes. When the timer reaches zero, we want to show the score to the user, and reset the game for another match. Let’s work on that!
First, we’re going to write another function. Add the following code to the class:
func finishGame()
{
}
The first thing this function needs to do, is to reset the timer. After all, the game is done, so the countdown can stop. We do that like this:
timer?.invalidate()
timer = nil
Make sure to add the above code to the function. It’ll first invalidate the timer, which means to stop it firing, and to release its closure. Then, the timer
property is set to nil
.
We already know from the code in inputFieldDidChange()
, that when timer
is nil
, it’ll start again when the user inputs their first answer. So, this is exactly how we need to “reset the board” to its initial state.
Next, add the following code to the function, below the previous lines of code:
let alert = UIAlertController(title: "Time's Up!", message: "Your time is up! You got a score of \(score) points. Awesome!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK, start new game", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
The above code creates a UIAlertController, which is a popup dialog that shows a bit of text and a button. It’ll tell the user their score, and prompts them to start another game. Finally, the alert
object is shown on screen with the present(_:animated:completion:)
function call.
Then, add the following code to the function, below the other lines:
score = 0
seconds = 60
Familiar lines of code, right? We’re resetting the score
and seconds
properties to their initial states. Because the score is shown to the user in the alert dialog, we can safely reset it to 0
.
And finally, add these lines of code to the end of the function:
updateTimeLabel()
updateScoreLabel()
updateNumberLabel()
These are the 3 functions we coded earlier. They’ll show the time and score in their respective labels. The updateNumberLabel()
call will generate a new random 4 digit number – and we’re done!
The last step we’ll need to take, is putting the finishGame()
function to use. Where do we need to add a call to it? We already know that the game is finished when the timer reaches zero, so… We’ll need to adjust the code of the timer!
First, make sure you’ve located the following block in your code:
if self.seconds <= 60 {
self.seconds -= 1
self.updateTimeLabel()
}
Next, what we want, is to merge the above code with something like this:
if self.seconds == 0 {
self.updateTimeLabel()
}
How do we do that? Well, first off, it’s important to understand that there’s overlap between the 2 conditionals. When seconds
is 0
, it matches both “less than or equal to 60” and “is zero”. Like this:
let seconds = 0
if seconds <= 60 {
// This is true
}
if seconds == 0 {
// This is also true
}
Can we just add an else if
clause to the conditional? Like this:
if seconds <= 60 {
// Is this true?
} else if seconds == 0 {
// Or is this true?
}
This won’t work. When we add if seconds == 0
below the other conditional, it’ll never execute. Because conditionals are evaluated top-to-bottom, if seconds
is zero, it’ll always match the first clause in the conditional! And our seconds == 0
code wouldn’t execute.
What’s the solution? We add it on top! That may seem counter-intuitive, but it’s a great solution. Change the timer’s closure to reflect the following:
timer = ··· { timer in
if self.seconds == 0 {
self.finishGame()
} else if self.seconds <= 60 {
self.seconds -= 1
self.updateTimeLabel()
}
}
Here’s how that works:
seconds
is 0
, the finishGame()
function is calledseconds
is less than or equal to 60
, seconds
is decreased by 1, and updateTimeLabel()
is calledNeat! There’s no conflict. And of course, if you’re curious, try both approaches, and see for yourself which one works OK.
You’ll have to ignore those self.
references for a bit – they don’t make the code easier to read. A future version of Swift may remove the requirement for explicit calls to self
, because in the above code block, it doesn’t have any benefits.
Sign up for my iOS development course, and learn how to build great iOS 14 apps with Swift 5 and Xcode 12.
We’re done with the app! AWESOME! Run your app with Command + R or by pressing the Play button in Xcode. Does it run OK? Well done!
This wouldn’t be a tutorial, without a challenge. See if you can change the game and its mechanics to solve these challenges:
GameKit
into the app, for score leaderboards. Who’s got the highest score?Want to learn more? Check out the following articles and tutorials:
Hi, I'm Reinder.
I help developers play with code.
Build great iOS apps
Learn how in my free 7-day course
No spam, ever. Unsubscribe anytime. Privacy Policy