The Startup
Published in

The Startup

The Ultimate Guide to Swift Conditionals

Photo by Caleb Jones on Unsplash

Swift is a beautiful language that was carefully designed and has greatly evolved since it was announced back in 2014. When you compare Swift to more traditional programming languages you can easily find elements they have in common but they are done in a specific way. A more Swifty-way.

Today, I want to cover one of those basic topics, that is well-known from other programming languages, but still has its own, unique implementation in Swift. Let’s get started with conditionals in Swift.

The if-statement

That’s an all-time classic when it comes to programming. If a condition is true, then the code in the block of the if-statement will run.

var appleCount = 3
var bananaCount = 1
if appleCount > bananaCount {
print("We have more apples than bananas!")
}

In the example above, the appleCount is higher than the bananaCount, therefore, the code gets executed and prints the statement.

Here we have already the first special flavour of Swift. We don’t need braces. They are completely optional. From my experience, I can tell you that indeed most iOS developers, including myself, omit the braces.

But it’s not just that! Swift always requires you to have a boolean expression. The Swift compiler won’t make an implicit comparison. Let me give you an example:

if appleCount {
print("We have apples > or < 0") // This code won't work!
}

In other programming languages like JavaScript, this code would be valid(with braces) and the print statement would execute. JavaScript would only check if it is a ‘truthy’ value. Other languages, would convert this implicitly to the comparisonappleCount != 0. But Swift is different in that regard.

Swift always wants you to be very thoughtful.

That’s why Swift doesn’t want to implicitally assume what you meant there. You could easily misinterpret what would happen when the value is negative or 0. That’s why you will get the following error message:

The error says ‘Type ‘Int’ cannot be used as a boolean; test for ‘!=0’ instead

Swift gives you a recommendation and a hint about what the compiler assumed you actually wanted to do. But it won’t do it for you. Again that’s a more robust way of programming where the language forces you to think about what you do and that’s how errors get avoided.

else and else if

The if-statment is often accompanied by the else- and else-if-statements. Here in code:

if appleCount > bananaCount {
print("We have more apples than bananas!")
} else if appleCount == bananaCount {
print("We have the same amount of apple and bananas!")
} else {
print("We have more bananas than apples!")
}

Let’s read it line-by-line. If the appleCount variable is bigger than the bananaCount variable, execute the first print statement. Only if that’s not the case check the else-if-statement. If that’s true, execute the second print statement. Lastly, in the case that no if or else-if condition was met, print the string ‘We have more bananas than apples!’.

There are four key takeaways here:

  1. You need no braces for else-if as well.
  2. An else-statement has no condition. It will always execute when none of the above if/else-if conditions were evaluated to true.
  3. You can have only one if- and one else-condition. But you can have an unlimited amount of else-if-statements.
  4. As soon as one condition evaluated to true, the following else-if-statements will not be evaluated! The code in their blocks will not be executed! Even if that condition would have been true as well! So be careful when multiple if-/else-if-statements could be true at the same time. Make sure that you have the correct order in place!

The if-let-statement

Here comes the first conditional that’s only available in Swift (as far as I know). The if-let-statement is used in order to unwrap an optional.

If that left you with more questions than answers, let me very briefly explain what an optional is.

An optional is a special type of a variable, constant or property that either has a value of its type or no value (nil).

In my opinion, an example is more helpful here:

var optionalText: String? = "Hi folks!"
optionalText = nil

You define an optional by adding a ? after the type definition. That means that the variable optionalText can either hold a value of type String or no value (nil).

Optionals are a centerpiece of the Swift programming language and I could write a whole new blog post about them. In the context of conditionals, you only have to know, that at runtime they need to be checked if the optional has a value or not. So Swift will make sure at compile-time, that all optionals are not accessed without unwrapping them.

Okay, back to the if-let-statement.

Again, Swift wants you to be very thoughtful about what you are doing. Therefore, it doesn’t let you access a value without unwrapping it in any way. Unwrapping means, you make sure that the optional really has a value and is not nil (that could cause your programs to crash).

And here comes if-let to the rescue. Let’s continue with the example of the optional above:

var optionalText: String? = "Hi folks!"
optionalText = nil
if let text = optionalText {
print("Hooray! We have a text: \(text)")
}

What would happen here? The print statement doesn’t get executed. We check if the value of optionalText is not nil and if that’s the case, we’d assign the value to the constant text. However, above we set the value to nil, so nothing gets printed out.

var optionalText: String? = nil
optionalText = "Hi folks!"
if let text = optionalText {
print("Hooray! We have a text: \(text)")
}

In the second example, we’ve switched the assignments of nil and "Hi folks! and therefore, the text gets printed to the screen. This kind of unwrapping an optional is called optional binding.

Note: Have you noticed that we haven’t accessed the variable optionalText in the if-let-statement? Instead we use the constant text that we’ve used to unwrap it. We could (if we are fine with having warnings in our project) use the optional directly as well. But that’s only the case because we use string interpolation. String interpolation will transform the text to ‘nil’ or 'Optional(…)’ where ‘…’ would be the textual description of the value. In all other cases you need some form of unwrapping.

The guard-statement

The guard-statement is another great Swift tool that was added in the language version 2.0. It’s often overlooked by beginners but I tend to prefer it over if-statements whenever possible (more on that later).

The guard acts like a bouncer in front of a famous club. If you’re able to fulfill his high expectations, you are allowed to get in and do your thing. But if his criteria aren’t met, he will send you back home where you were coming from.

Let me show it in code:

func danceInTheClub() {
let goodShoes = true
let
niceHair = true
let
tieToMatchTheShirt = false
guard goodShoes && niceHair && tieToMatchTheShirt else {
return
}

print("Yeah, I can dance all night!")
}

If we call the functiondanceInTheClub() the print statement won’t get executed. We have no tie that matches our shirt and therefore, the guard, won’t let us proceed. He simply returns from the function.

Here are a few key takeaways from guard-statements:

  1. The guard-statement only executes its else-block, when the criteria is not met!
  2. The guard-statement uses an else at the end. This is mandatory to form a valid guard and is what happens on exit!
  3. The code inside the guard-statement must (!!!) exit its scope. So you need one of the following: return, continue, break, or throw.
  4. The code after the guard gets only executed if all guard conditions were evaluated to true.

Note: You can combine multiple conditions in one guard/if/etc. by using the &&- or logical AND-operator. It starts from left to right and checks one after the other. Only if all evaluate to true, the whole expression will evaluate as true. If you need only one of multiple conditions to be true, then use the || or logical OR-operator.

The guard-let-statement

Now, you probably already have a solid enough foundation to figure out what that statement does. It’s a combination of the guard- and the if-let-statement.

It also lets you unwrap an optional by creating an optional binding and only runs the code after the guard if the optional had a value, otherwise, it will exit the scope.

Back to code:

func printNames() {
let firstName = "Manuel"
let lastName = "Schulze"
let middleName: String? = nil
print("My first name is: \(firstName)")
print("My last name is: \(lastName)")
guard let midName = middleName else {
return
}
print("My middle name is: \(midName)")
}

The guard-let-statement above will not allow us to print the middle name as I don’t have one. If I would have one, the optional would again be unwrapped and we could go on and run the code that follows the guard.

Why do I prefer guard over if?

The guard-statement solves in my opinion one concrete issue with if-statements.

As always, I provide you with some code. First, imagine there wouldn’t be a guard-statement in Swift:

func playAndRecordSoccer() {
let haveSoccerShoes = true
let
haveBall = true
let
haveCamera = true
if haveBall {
print("We take our ball!")
if haveSoccerShoes {
print("We take our shoes!")
if haveCamera {
print("We take our camera and record our crazy goals!")
}
}
}
}

I guess you can already see, what I dislike about if-statements. You can easily end up with a very deeply nested code that gets hard to read and reason about.

Note: Without the first two print statements, the code above could also be done without nesting by simply combining the conditions with the logical AND-operator (&&).

Now let’s rewrite the function with guard-statements:

func playAndRecordSoccerGuard() {
let haveSoccerShoes = true
let
haveBall = true
let
haveCamera = true
guard haveBall else { return }
print("We get our ball!")
guard haveSoccerShoes else { return }
print("We get our shoes!")
guard haveCamera else { return }
print("We get our camera and record our crazy goals!")
}

Now we have all our code without hard to read nesting. Thanks, guard-statements!

Note: I also prefer guard over if because I like to separate all the criteria into their own line if they are not highly related to each other. With if-statements, I would again need to nest them. With guards, I can have one per line and my code is still flat and readable.

Provide default-values with the nil-coalescing operator ??

After we’ve covered two of the more complex conditional statements, let us now look at something easier to grasp.

The nil-coalescing operator (??). It is again something that is used when it comes to optionals but in this case, helps by providing default values. So if there is nil on the left side of the operator, it will use the default parameter on the right side. It gets clearer in code, so let’s dive into it:

func loadUsername() -> String? {
// Some code could be here that either returns a string or nil
return nil
}
var usernameFromServer: String? = loadUsername()
var username = usernameFromServer ?? "stranger"
print("Hello \(username)")

Imagine that the loadUsername() makes a request for a user object that is either set or not. It will return nil if there is no user. In our code, it always returns nil instead of a real username. Here the ??-operator comes in handy, as it will assign the value on the right (“stranger”) to the variable username when the left operand is nil.

We would print “Hello stranger” to the screen.

The ternary-operator

Another simple operator that can be used inline is the ternary-operator. It is supported by a wide range of programming languages and you’ve probably seen it before.

First, why is it called the ternary-operator? Because it has three operands. In the order of their appearance (from left to right), they are:

  1. The condition that should be checked.
  2. The ‘happy-path’. So the value that is used when the condition is evaluated to true.
  3. The ‘unhappy-path’. The value that is used when the condition is evaluated to false.

As always, let’s look at an example:

let limit = 0.5
let outOfTolerance = 0.6
var color: UIColor = outOfTolerance <= limit ? .green : .red

Let’s assume, that we are working on an app for measurements and there is a tolerance limit (limit) of 0.5. If our actual measurement (outOfTolerance) is less or equal than the limit, we display the value as text with a font color of green. If it’s higher than the limit, we want to display it in red. The code above would do just that and in this case (we are 0.6 out of tolerance) the variable color would be red.

Note: You may think that there are four operands in the expression above. But here it helps to use braces to illustrate what the operands are:

var color: UIColor = (outOfTolerance <= limit) ? .green : .red

The first operand is on the left of the ?, that’s the logical expression that gets evaluated. After the ? comes the second operand, the value that gets used when the condition gets evaluated to true. Finally, after the : we have the third operand that is the value that gets used when the condition evaluates to false.

The switch-statement

The switch-statement is again an all-time classic when it comes to structured programming. But it also has special flavors in Swift, so let’s talk about it in more detail.

A switch-statement checks for equality. Let’s look at a code example and talk about it afterward.

let favoriteFruitManuel = "Bananas"
let yourFavoriteFruit = "Watermelons"
switch yourFavoriteFruit {
case favoriteFruitManuel:
print("BANANA!")
case "Apples":
print("An apple a day, keep the doctor away!")
default:
print("You have a unique taste and like something else!")
}

Let’s go through it:

  • A switch statement can have a boolean expression but usually is used to check for equality. In the example, you can see that we check if yourFavoriteFruit is equal to favoriteFruitManuel or the string "Apples”.
  • That means you can compare them to constants, variables, or properties.
  • But also check for matches with literals (42, “Hello”, true, 3.14159, etc.).
  • The case-statements have no indentation. They are usually on the same level as the switch statement. The block of code that gets executed if one case evaluates to true doesn't have braces but is indented.
  • You need to provide one case-statement for each case that could occur.
  • However, if that’s an unlimited amount of cases or you just don’t want/have to provide a case-statement for each case, you can use the default-case, which works like the else-statement in if-else. More on the default-case later.
  • Unlike many other languages, you don’t need a break at the end of the case to avoid that your program calls every single case that follows (falls through).

Note: The last one sounds pretty absurd. But that’s actually how many programming languages handle switch-statements and a common source of errors. In these programming languages as soon as you have one match, all code in the following case-statements gets executed until a break was found. You can easily forget one and wonder why two cases are executed at the same time. But don’t care, Swift got your back!

However, if you want that behavior you need to tell that Swift explicitly by using the fallthrough keyword. It will fall through to the following case.

You also don’t have to duplicate code if several cases-blocks look the same. You can just separate them with a ,.

enum Days {
case monday, tuesday, wednesday, thursday, friday
case saturday, sunday
}
let today = Days.saturdayswitch today {
case .saturday, .sunday:
print("Hooray weekend!")
default:
print("Yeah, coding time!")
}

Here we have two interesting things.

  1. You can see, how we separate multiple values in a case-statement with case .saturday, .sunday:. They share the code that gets executed when there is a match of today with one of them.
  2. Notice, that this is where switch-statements really shine. They are perfect in conjunction with enums. For an enum, you provide a fixed amount of cases (don’t confuse them with switch-cases) that can occur. Like days in a week.

Note: Why a switch-statement works so great with enums? Imagine, you have a fixed amount of cases, for example, errors that can occur. Now you handle all of them specifically in your switch-statement (no default-case). Suddenly, your project manager approaches you to add another error case that should be handled as well. You add it to your enum and your project doesn't compile anymore!

Great!

No really, now you see where the specific handling is missing and you can act upon that information and write your custom code for that error. With if-statements, you wouldn’t get any information that there is one case missing. You would need to remember all places where the changes are now needed.

Switch-statements: Optionals

So we looked at if and if-let. We’ve also seen guard and guard-let. Well, we have no switch and switch-let but case and case-let.

It works the same as the other conditionals. Case-let-statements create an optional binding and if the optional has a value it will be unwrapped and the case block gets executed. However, mostly you will see it combined with a where-clause.

Let me give you an example of that:

let favoriteBook: String? = "Clean Coder"switch favoriteBook {
case let book where book == "Clean Coder":
print("A great book that teaches professionalism as a coder")
case let book where book == "Refactoring":
print("A great book that teaches you how to improve your code")
default:
print("Another favorite book or none")
}

You make two things at once here. First, you unwrap the optional or create an optional binding. If the constant favoriteBook has a value and that value is “Clean Coder” it will match the first case. For a value of “Refactoring” it will match the second. In all other cases (another String or nil) it will print “Another favorite book or none”.

That’s already pretty handy, but I’m not the biggest fan of the where-clause. So we can use a different approach. For that, you need to know, that an optional is under the hood just an enum. It has two cases, that look roughly like this:some(value) and none.

With that we can write some really neat code:

let cleanCoder = "Clean Coder"
let refactoring = "Refactoring"
let favoriteBook: String? = nil
switch favoriteBook {
case .some(cleanCoder):
print("\(cleanCoder): Professionalism as a coder")
case .some(refactoring):
print("\(refactoring): How to improve your code")
case .some(let val):
print("You love another book: \(val)")
default:
print("You have no favorite book.")
}

For me, that is a lot more readable and we can even check if a value is set at all.

Switch-statements: Check for ranges

You are not only able to check for exact matches, but you can also check for ranges.

Code speaks more than a thousand words, here we go:

let monthOfBirth = 11switch monthOfBirth {
case ..<7:
print("You are born in the first half of the year")
case 7...12:
print("You are born in the second half of the year")
default:
print("That's not a valid month!")
}

I won’t cover now how these ranges work in detail, this is part of another story. But here are the key takeaways:

  1. We check for a range that is less than 7 (July) with ..<7.
  2. Then we check if the in the second case if the value is between 7 and 12 by using 7…12.
  3. Lastly, we cover the default case. It’s not enough that we know, that these values will never be above 12. The Swift compiler doesn’t know that. He only sees an integer and that means it could be a much bigger range than 1–12. The switch wouldn’t be exhaustive, that’s why we need a default-case.

Spot the bug: Speaking of integers, can you spot the error in the example above? In the first case, we only check if the value is below 7. Well, that means -1 would also be allowed there and not throw the default-case but instead the first case. You can avoid that by either writing 1…7 or using an unsigned integer (UInt) that can only hold positive integer values (be careful, 0 would still be possible). I would prefer the first option as it is recommended by Swift to mostly use the type Int for readability and interoperatability and we don’t have to handle the 0 case specifically.

Switch-statements: Do I need a default-case?

The last point that I want to discuss is when do you need a default-case?

It’s actually quite easy. When you provide all possible options, you don’t need one. The switch-statement is exhaustive and knows how to handle each situation. Frequently, you will find yourself not being able to provide all cases (e.g. when checking for values of type String or numbers). Then you will need a default-case.

Additionally, when you don’t need to specify handling for all cases (even if there are just 2 or 3), it’s completely fine to write just the cases explicitly that you need and catch all other occurrences with the default-case.

A final code example:

let isRaining = falseswitch isRaining {
case true:
print("Get my umbrella!")
case false:
print("Get my sunglasses")
}
let yourAnswerToTheMeaningOfLife = 42switch yourAnswerToTheMeaningOfLife {
case 42:
print("That's true 42 is the answer to the question of life")
default:
print("No that's wrong! It's 42!")
}

In the first switch-statement, we provide all possible values for equality checks in the case-statements. The constant isRaining can only be true or false. Therefore, we don’t need the default case.

In the second switch-statement, there is no way to provide all values (at least if you don’t want to type more than 4 billion cases). So we just handle our specific case (42) and let the others fall through to the default case.

What would happen if we omit the default-case in the last switch-statement?

We would get an error by Swift:

The error says: ‘Swift must be exhaustive`.

Summary

We’ve covered a lot of different conditionals today that should be part of your tool belt while becoming an expert in Swift.

There are different scenarios when you want to use them. Therefore, let me give you a brief overview of all of them:

  1. If-statements run the code in their block when their condition evaluates to true. They can have an additional else-if-statement that checks if another condition is true and runs its code if that's the case. If none of the if- and else-if-statements evaluated to true, the optional else case would run.
  2. The if-let-statement works like an if-statement but creates an optional binding. If the optional has a value, the code of the if-let will run and the value is unwrapped in the conditional binding constant.
  3. The guard-statement acts like a bouncer. It will only let you continue to run the code in the scope if the condition evaluates to true. It has always an else-case in which the code must exit (with return, continue, break, or throw).
  4. The guard-let-statement also creates an optional binding. If the optional has a value, it will skip the guard’s else and run the code that follows. You can use the constant of the optional binding to have the value unwrapped until you reach the end of the scope.
  5. The ??-operator allows you to provide default values, for the case that the operand on the left is nil.
  6. The ternary operator lets you inline a conditional assignment where you want to use one of two values.
  7. The switch statement lets you check for equality of the expression/value with different cases. You don’t need a break in the cases to avoid a falling through in Swift. Swift will always only run the code in the case that was matched. You can (and oftentimes have to) provide a default case, that will execute some code when no case was evaluated to true.

That’s it for today. Are there any more tips and tricks about conditionals that I haven’t covered, but you love to use? Have I even forgotten a conditional? Let me know in the comments! Can’t wait to hear from you!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Manuel Schulze

Manuel Schulze

iOS developer from Germany who works as a contractor. Learning new things daily. Teaching Swift, iOS, macOS, watchOS, iPadOS, and tvOS programming.