Skip to main content

Day 05

About 17 minSwiftcrashcoursepaul-hudsonswiftswiftuihacking-with-swiftxcodeappstore

Day 05 ź“€ė Ø


100 Days of SwiftUI - Day 5

Conditions

Conditions

If thereā€™s one line from Shakespeareā€™s Hamlet that most people know, itā€™s ā€œto be or not to be, that is the question.ā€ Shakespeare meant that as the ultimate question of life or death, but it just so happens that Hamlet strikes right at the heart of logic in programming: evaluating whether a condition is true or not.

Today weā€™re going to get into the real detail of Swift: operators and conditions, which let us evaluate our programā€™s state as it runs and take different action depending on the result. There are several ways of doing it and youā€™ll need them all, but weā€™ll walk through them step by step so you can see how they compare.

Today you have four tutorials to work through, where youā€™ll meet things like if, switch, and more. Once youā€™ve watched each video and optionally gone through the extra reading, thereā€™s a short test to help make sure youā€™ve understood what was taught.

1. How to check a condition is true or false

1. How to check a condition is true or false
100 Days of SwiftUI - Day 5 - 1. How to check a condition is true or false

1. How to check a condition is true or false

Programs very often make choices:

  • If the studentā€™s exam score was over 80 then print a success message.
  • If the user entered a name that comes after their friendā€™s name alphabetically, put the friendā€™s name first.
  • If adding a number to an array makes it contain more than 3 items, remove the oldest one.
  • If the user was asked to enter their name and typed nothing at all, give them a default name of ā€œAnonymousā€.

Swift handles these with if statements, which let us check a condition and run some code if the condition is true. They look like this:

if someCondition {
    print("Do something")
}

Letā€™s break that down:

  1. The condition starts with if, which signals to Swift we want to check some kind of condition in our code.
  2. The someCondition part is where you write your condition ā€“ was the score over 80? Does the array contain more than 3 items?
  3. If the condition is true ā€“ if the score really is over 80 ā€“ then we print the ā€œDo somethingā€ message.

Of course, that isnā€™t everything in the code: I didnā€™t mention the little { and } symbols. These are called braces ā€“ opening and closing braces, more specifically ā€“ although sometimes youā€™ll hear them referred to as ā€œcurly bracesā€ or ā€œcurly bracketsā€.

These braces are used extensively in Swift to mark blocks of code: the opening brace starts the block, and the closing brace ends it. Inside the code block is all the code we want to run if our condition happens to be true when itā€™s checked, which in our case is printing a message.

You can include as much code in there as you want:

if someCondition {
    print("Do something")
    print("Do something else")
    print("Do a third thing")
}

Of course, what really matters is the someCondition part, because thatā€™s where your checking code comes in: what condition do you actually want to check?

Well, letā€™s try the score example: if a score constant is over 80, letā€™s print a message. Hereā€™s how that would look in code:

let score = 85

if score > 80 {
    print("Great job!")
}

In that code, score > 80 is our condition. Youā€™ll remember > from school meaning ā€œis greater thanā€, so our complete condition is ā€œif score is greater than 80.ā€ And if it is greater than 80, ā€œGreat job!ā€ will be printed ā€“ nice!

That > symbol is a comparison operator, because it compares two things and returns a Boolean result: is the thing on the left greater than the thing on the right? You can also use < for less than, >= for ā€œgreater than or equalā€, and <= for ā€œless than or equal.ā€

Letā€™s try it out ā€“ what do you think this code will print?

let speed = 88
let percentage = 85
let age = 18

if speed >= 88 {
    print("Where we're going we don't need roads.")
}

if percentage < 85 {
    print("Sorry, you failed the test.")
}

if age >= 18 {
    print("You're eligible to vote")
}

Try and run the code mentally in your head ā€“ which print() lines will actually be run?

Well, our first one will run if speed is greater than or equal to 88, and because it is exactly 88 the first print() code will be run.

The second one will run if percentage is less than 85, and because it is exactly 85 the second print() will not run ā€“ we used less than, not less than or equal.

The third will run if age is greater than or equal to 18, and because itā€™s exactly 18 the third print() will run.

Now letā€™s try our second example condition: if the user entered a name that comes after their friendā€™s name alphabetically, put the friendā€™s name first. Youā€™ve seen how <, >= and others work great with numbers, but they also work equally well with strings right out of the box:

let ourName = "Dave Lister"
let friendName = "Arnold Rimmer"

if ourName < friendName {
    print("It's \(ourName) vs \(friendName)")
}

if ourName > friendName {
    print("It's \(friendName) vs \(ourName)")
}

So, if the string inside ourName comes before the string inside friendName when sorted alphabetically, it prints ourName first then friendName, exactly as we wanted.

Letā€™s take a look at our third example condition: if adding a number to an array makes it contain more than 3 items, remove the oldest one. Youā€™ve already met append(), count, and remove(at:), so we can now put all three together with a condition like this:

// Make an array of 3 numbers
var numbers = [1, 2, 3]

// Add a 4th
numbers.append(4)

// If we have over 3 items
if numbers.count > 3 {
    // Remove the oldest number
    numbers.remove(at: 0)
}

// Display the result
print(numbers)

Now letā€™s look at our fourth example condition: if the user was asked to enter their name and typed nothing at all, give them a default name of ā€œAnonymousā€.

To solve this youā€™ll first need to meet two other comparison operators youā€™ll use a lot, both of which handle equality. The first is == and means ā€œis equal to,ā€ which is used like this:

let country = "Canada"

if country == "Australia" {
    print("G'day!")
}

The second is !=, which means ā€œis not equal toā€, and is used like this:

let name = "Taylor Swift"

if name != "Anonymous" {
    print("Welcome, \(name)")
}

In our case, we want to check whether the username entered by the user is empty, which we could do like this:

// Create the username variable
var username = "taylorswift13"

// If `username` contains an empty string
if username == "" {
    // Make it equal to "Anonymous"
    username = "Anonymous"
}

// Now print a welcome message
print("Welcome, \(username)!")

That "" is an empty string: we start the string and end the string, with nothing in between. By comparing username to that, weā€™re checking if the user also entered an empty string for their username, which is exactly what we want.

Now, there are other ways of doing this check, and itā€™s important you understand what they do.

First, we could compare the count of the string ā€“ how many letters it has ā€“ against 0, like this:

if username.count == 0 {
    username = "Anonymous"
}

Comparing one string against another isnā€™t very fast in any language, so weā€™ve replaced the string comparison with an integer comparison: does the number of letters in the string equal 0?

In many languages thatā€™s very fast, but not in Swift. You see, Swift supports all sorts of complex strings ā€“ literally every human language works out of the box, including emoji, and that just isnā€™t true in so many other programming languages. However, this really great support has a cost, and one part of that cost is that asking a string for its count makes Swift go through and count up all the letters one by one ā€“ it doesnā€™t just store its length separately from the string.

So, think about the situation where you have a massive string that stores the complete works of Shakespeare. Our little check for count == 0 has to go through and count all the letters in the string, even though as soon as we have counted at least one character we know the answer to our question.

As a result, Swift adds a second piece of functionality to all its strings, arrays, dictionaries, and sets: isEmpty. This will send back true if the thing youā€™re checking has nothing inside, and we can use it to fix our condition like this:

if username.isEmpty == true {
    username = "Anonymous"
}

Thatā€™s better, but we can go one step further. You see, ultimately what matters is that your condition must boil down to either true or false; Swift wonā€™t allow anything else. In our case, username.isEmpty is already a Boolean, meaning it will be true or false, so we can make our code even simpler:

if username.isEmpty {
    username = "Anonymous"
}

If isEmpty is true the condition passes and username gets set to Anonymous, otherwise the condition fails.

1. How to check a condition is true or false - Additional

2. How to check multiple conditions

2. How to check multiple conditions
100 Days of SwiftUI - Day 5 - 2. How to check multiple conditions

2. How to check multiple conditions

When we use if we must provide Swift some kind of condition that will either be true or false once it has been evaluated. If you want to check for several different values, you can place them one after the other like this:

let age = 16

if age >= 18 {
    print("You can vote in the next election.")
}

if age < 18 {
    print("Sorry, you're too young to vote.")
}

However, thatā€™s not very efficient if you think about it: our two conditions are mutually exclusive, because if someone is greater than or equal to 18 (the first condition) then they canā€™t be less than 18 (the second condition), and the opposite is also true. Weā€™re making Swift do work that just isnā€™t needed.

In this situation, Swift provides us with a more advanced condition that lets us add an else block to our code ā€“ some code to run if the condition is not true.

Using else we could rewrite our previous code to this:

let age = 16

if age >= 18 {
    print("You can vote in the next election.")
} else {
    print("Sorry, you're too young to vote.")
}

Now Swift only needs to check age once: if itā€™s greater than or equal to 18 the first print() code is run, but if itā€™s any value less than 18 the second print() code is run.

So, now our condition looks like this:

if someCondition {
    print("This will run if the condition is true")
} else {
    print("This will run if the condition is false")
}

Thereā€™s an even more advanced condition called else if, which lets you run a new check if the first one fails. You can have just one of these if you want, or have multiple else if, and even combine else if with an else if needed. However, you can only ever have one else, because that means ā€œif all the other conditions have been false.ā€

Hereā€™s how that looks:

let a = false
let b = true

if a {
    print("Code to run if a is true")
} else if b {
    print("Code to run if a is false but b is true")
} else {
    print("Code to run if both a and b are false")
}

You can keep on adding more and more else if conditions if you want, but watch out that your code doesnā€™t get too complicated!

As well as using else and else if to make more advanced conditions, you can also check more than one thing. For example, we might want to say ā€œif todayā€™s temperature is over 20 degrees Celsius but under 30, print a message.ā€

This has two conditions, so we could write this:

let temp = 25

if temp > 20 {
    if temp < 30 {
        print("It's a nice day.")
    }
}

Although that works well enough, Swift provides a shorter alternative: we can use && to combine two conditions together, and the whole condition will only be true if the two parts inside the condition are true.

So, we could change our code to this:

if temp > 20 && temp < 30 {
    print("It's a nice day.")
}

You should read && as ā€œandā€, so our whole conditions reads ā€œif temp is greater than 20 and temp is less than 30, print a message.ā€ Itā€™s called a logical operator because it combines Booleans to make a new Boolean.

&& has a counterpart that is two pipe symbols, ||, which means ā€œorā€. Whereas && will only make a condition be true if both subconditions are true, || will make a condition be true if either subcondition is true.

For example, we could say that a user can buy a game if they are at least 18, or if they are under 18 they must have permission from a parent. We could write that using || like so:

let userAge = 14
let hasParentalConsent = true

if userAge >= 18 || hasParentalConsent == true {
    print("You can buy the game")
}

That will print ā€œYou can buy the gameā€, because although the first half of our condition fails ā€“ the user is not at least 18 ā€“ the second half passes, because they do have parental consent.

Remember, using == true in a condition can be removed, because weā€™re obviously already checking a Boolean. So, we could write this instead:

if userAge >= 18 || hasParentalConsent {
    print("You can buy the game")
}

To finish up with checking multiple conditions, letā€™s try a more complex example that combines if, else if, else, and || all at the same time, and even shows off how enums fit into conditions.

In this example weā€™re going to create an enum called TransportOption, which contains five cases: airplane, helicopter, bicycle, car, and scooter. Weā€™ll then assign an example value to a constant, and run some checks:

  • If we are going somewhere by airplane or by helicopter, weā€™ll print ā€œLetā€™s fly!ā€
  • If weā€™re going by bicycle, weā€™ll print ā€œI hope thereā€™s a bike pathā€¦ā€
  • If weā€™re going by car, weā€™ll print ā€œTime to get stuck in traffic.ā€
  • Otherwise weā€™ll print ā€œIā€™m going to hire a scooter now!ā€

Hereā€™s the code for that:

enum TransportOption {
    case airplane, helicopter, bicycle, car, scooter
}

let transport = TransportOption.airplane

if transport == .airplane || transport == .helicopter {
    print("Let's fly!")
} else if transport == .bicycle {
    print("I hope there's a bike pathā€¦")
} else if transport == .car {
    print("Time to get stuck in traffic.")
} else {
    print("I'm going to hire a scooter now!")
}

Iā€™d like to pick out a few parts of that code:

  1. When we set the value for transport we need to be explicit that weā€™re referring to TransportOption.airplane. We canā€™t just write .airplane because Swift doesnā€™t understand we mean the TransportOption enum.
  2. Once that has happened, we donā€™t need to write TransportOption any more because Swift knows transport must be some kind of TransportOption. So, we can check whether itā€™s equal to .airplane rather than TransportOption.airplane.
  3. The code using || to check whether transport is equal to .airplane or equal to .helicopter, and if either of them are true then the condition is true, and ā€œLetā€™s fly!ā€ is printed.
  4. If the first condition fails ā€“ if the transport mode isnā€™t .airplane or .helicopter ā€“ then the second condition is run: is the transport mode .bicycle? If so, ā€œI hope thereā€™s a bike pathā€¦ā€ is printed.
  5. If we arenā€™t going by bicycle either, then we check whether weā€™re going by car. If we are, ā€œTime to get stuck in traffic.ā€ is printed.
  6. Finally, if all the previous conditions fail then the else block is run, and it means weā€™re going by scooter.

3. How to use switch statements to check multiple conditions

3. How to use switch statements to check multiple conditions
100 Days of SwiftUI - Day 5 - 3. How to use switch statements to check multiple conditions

3. How to use switch statements to check multiple conditions

You can use if and else if repeatedly to check conditions as many times as you want, but it gets a bit hard to read. For example, if we had a weather forecast from an enum we could choose which message to print based on a series of conditions, like this:

enum Weather {
    case sun, rain, wind, snow, unknown
}

let forecast = Weather.sun

if forecast == .sun {
    print("It should be a nice day.")
} else if forecast == .rain {
    print("Pack an umbrella.")
} else if forecast == .wind {
    print("Wear something warm")
} else if forecast == .rain {
    print("School is cancelled.")
} else {
    print("Our forecast generator is broken!")
}

That works, but it has problems:

  1. We keep having to write forecast, even though weā€™re checking the same thing each time.
  2. I accidentally checked .rain twice, even though the second check can never be true because the second check is only performed if the first check failed.
  3. I didnā€™t check .snow at all, so weā€™re missing functionality.

We can solve all three of those problems using a different way of checking conditions called switch. This also lets us check individual cases one by one, but now Swift is able to help out. In the case of an enum, it knows all possible cases the enum can have, so if we miss one or check one twice it will complain.

So, we can replace all those if and else if checks with this:

switch forecast {
case .sun:
    print("It should be a nice day.")
case .rain:
    print("Pack an umbrella.")
case .wind:
    print("Wear something warm")
case .snow:
    print("School is cancelled.")
case .unknown:
    print("Our forecast generator is broken!")
}

Letā€™s break that down:

  1. We start with switch forecast, which tells Swift thatā€™s the value we want to check.
  2. We then have a string of case statements, each of which are values we want to compare against forecast.
  3. Each of our cases lists one weather type, and because weā€™re switching on forecast we donā€™t need to write Weather.sun, Weather.rain and so on ā€“ Swift knows it must be some kind of Weather.
  4. After each case, we write a colon to mark the start of the code to run if that case is matched.
  5. We use a closing brace to end the switch statement.

If you try changing .snow for .rain, youā€™ll see Swift complains loudly: once that weā€™ve checked .rain twice, and again that our switch statement is not exhaustive ā€“ that it doesnā€™t handle all possible cases.

If youā€™ve ever used other programming languages, you might have noticed that Swiftā€™s switch statement is different in two places:

  1. All switch statements must be exhaustive, meaning that all possible values must be handled in there so you canā€™t leave one off by accident.
  2. Swift will execute the first case that matches the condition youā€™re checking, but no more. Other languages often carry on executing other code from all subsequent cases, which is usually entirely the wrong default thing to do.

Although both those statements are true, Swift gives us a little more control if we need it.

First, yes all switch statements must be exhaustive: you must ensure all possible values are covered. If youā€™re switching on a string then clearly itā€™s not possible to make an exhaustive check of all possible strings because there is an infinite number, so instead we need to provide a default case ā€“ code to run if none of the other cases match.

For example, we could switch over a string containing a place name:

let place = "Metropolis"

switch place {
case "Gotham":
    print("You're Batman!")
case "Mega-City One":
    print("You're Judge Dredd!")
case "Wakanda":
    print("You're Black Panther!")
default:
    print("Who are you?")
}

That default: at the end is the default case, which will be run if all cases have failed to match.

Remember: Swift checks its cases in order and runs the first one that matches. If you place default before any other case, that case is useless because it will never be matched and Swift will refuse to build your code.

Second, if you explicitly want Swift to carry on executing subsequent cases, use fallthrough. This is not commonly used, but sometimes ā€“ just sometimes ā€“ it can help you avoid repeating work.

For example, thereā€™s a famous Christmas song called The Twelve Days of Christmas, and as the song goes on more and more gifts are heaped on an unfortunate person who by about day six has a rather full house.

We could make a simple approximation of this song using fallthrough. First, hereā€™s how the code would look without fallthrough:

let day = 5
print("My true love gave to meā€¦")

switch day {
case 5:
    print("5 golden rings")
case 4:
    print("4 calling birds")
case 3:
    print("3 French hens")
case 2:
    print("2 turtle doves")
default:
    print("A partridge in a pear tree")
}

That will print ā€œ5 golden ringsā€, which isnā€™t quite right. On day 1 only ā€œA partridge in a pear treeā€ should be printed, on day 2 it should be ā€œ2 turtle dovesā€ then ā€œA partridge in a pear treeā€, on day 3 it should be ā€œ3 French hensā€, ā€œ2 turtle dovesā€, andā€¦ well, you get the idea.

We can use fallthrough to get exactly that behavior:

let day = 5
print("My true love gave to meā€¦")

switch day {
case 5:
    print("5 golden rings")
    fallthrough
case 4:
    print("4 calling birds")
    fallthrough
case 3:
    print("3 French hens")
    fallthrough
case 2:
    print("2 turtle doves")
    fallthrough
default:
    print("A partridge in a pear tree")
}

That will match the first case and print ā€œ5 golden ringsā€, but the fallthrough line means case 4 will execute and print ā€œ4 calling birdsā€, which in turn uses fallthrough again so that ā€œ3 French hensā€ is printed, and so on. Itā€™s not a perfect match to the song, but at least you can see the functionality in action!

3. How to use switch statements to check multiple conditions - Additional

4. How to use the ternary conditional operator for quick tests

4. How to use the ternary conditional operator for quick tests
100 Days of SwiftUI - Day 5 - 4. How to use the ternary conditional operator for quick tests

4. How to use the ternary conditional operator for quick tests

Thereā€™s one last way to check conditions in Swift, and when youā€™ll see it chances are youā€™ll wonder when itā€™s useful. To be fair, for a long time I very rarely used this approach, but as youā€™ll see later itā€™s really important with SwiftUI.

This option is called the ternary conditional operator. To understand why it has that name, you first need to know that +, -, ==, and so on are all called binary operators because they work with two pieces of input: 2 + 5, for example, works with 2 and 5.

Ternary operators work with three pieces of input, and in fact because the ternary conditional operator is the only ternary operator in Swift, youā€™ll often hear it called just ā€œthe ternary operator.ā€

Anyway, enough about names: what does this actually do? Well, the ternary operator lets us check a condition and return one of two values: something if the condition is true, and something if itā€™s false.

For example, we could create a constant called age that stores someoneā€™s age, then create a second constant called canVote that will store whether that person is able to vote or not:

let age = 18
let canVote = age >= 18 ? "Yes" : "No"

When that code runs, canVote will be set to ā€œYesā€ because age is set to 18.

As you can see, the ternary operator is split into three parts: a check (age >= 18), something for when the condition is true (ā€œYesā€), and something for when the condition is false (ā€œNoā€). That makes it exactly like a regular if and else block, in the same order.

If it helps, Scott Michaudopen in new window suggested a helpful mnemonic: WTF. It stands for ā€œwhat, true, falseā€, and matches the order of our code:

  • What is our condition? Well, itā€™s age >= 18.
  • What to do when the condition is true? Send back ā€œYesā€, so it can be stored in canVote.
  • And if the condition is false? Send back ā€œNoā€.

Letā€™s look at some other examples, start with an easy one that reads an hour in 24-hour format and prints one of two messages:

let hour = 23
print(hour < 12 ? "It's before noon" : "It's after noon")

Notice how that doesnā€™t assign the result anywhere ā€“ either the true or false case just gets printed depending on the value of hour.

Or hereā€™s one that reads the count of an array as part of its condition, then sends back one of two strings:

let names = ["Jayne", "Kaylee", "Mal"]   
let crewCount = names.isEmpty ? "No one" : "\(names.count) people"
print(crewCount)

It gets a little hard to read when your condition use == to check for equality, as you can see here:

enum Theme {
    case light, dark
}

let theme = Theme.dark

let background = theme == .dark ? "black" : "white"
print(background)

The = theme == part is usually the bit folks find hard to read, but remember to break it down:

  • What? theme == .dark
  • True: ā€œblackā€
  • False: ā€œwhiteā€ So if theme is equal to .dark return ā€œBlackā€, otherwise return ā€œWhiteā€, then assign that to background.

Now, you might be wondering why the ternary operator is useful, particularly when we have regular if/else conditions available to us. I realize itā€™s not a great answer, but youā€™ll have to trust me on this: there are some times, particularly with SwiftUI, when we have no choice and must use a ternary.

You can see roughly what the problem is with our code to check hours:

let hour = 23
print(hour < 12 ? "It's before noon" : "It's after noon")

If we wanted to write that out using if and else weā€™d either need to write this invalid code:

print(
    if hour < 12 {
        "It's before noon"
    } else {
        "It's after noon"
    }
)

Or run print() twice, like this:

if hour < 12 {
    print("It's before noon")
} else {
    print("It's after noon")
}

That second one works fine here, but it becomes almost impossible in SwiftUI as youā€™ll see much later. So, even though you might look at the ternary operator and wonder why youā€™d ever use it, please trust me: it matters!

4. How to use the ternary conditional operator for quick tests - Additional

Do you remember the two rules of this series? Youā€™re already being awesome at the first one because you keep coming back for more (you rock!), but donā€™t forget the second: post your progress online, so you can benefit from all the encouragement.


ģ“ģ°¬ķ¬ (MarkiiimarK)
Never Stop Learning.