Swift Control Flow

Regardless of the programming language used, application development is largely an exercise in applying logic, and much of the art of programming involves writing code that makes decisions based on one or more criteria. Such decisions define which code gets executed, how many times it is executed and, conversely, which code gets by-passed when the program is executing. This is often referred to as control flow since it controls the flow of program execution. Control flow typically falls into the categories of looping control (how often code is executed) and conditional control flow (whether code is executed). This chapter will provide an introductory overview of both types of control flow in Swift.

Looping Control Flow

This chapter will begin by looking at control flow in the form of loops. Loops are essentially sequences of Swift statements that are to be executed repeatedly until a specified condition is met. The first looping statement we will explore is the for-in loop.

The Swift for-in Statement

The for-in loop is used to iterate over a sequence of items contained in a collection or number range and provides a simple-to-use looping option.

The syntax of the for-in loop is as follows:

for constant name in collection or range {
    // code to be executed
}

In this syntax, constant name is the name to be used for a constant that will contain the current item from the collection or range through which the loop is iterating. The code in the body of the loop will typically use this constant name as a reference to the current item in the loop cycle. The collection or range references the item through which the loop is iterating. This could, for example, be an array of string values, a range operator or even a string of characters (the topic of collections will be covered in greater detail within the chapter entitled “Working with Array and Dictionary Collections in Swift”).

Consider, for example, the following for-in loop construct:

for index in 1...5 {
      print("Value of index is \(index)")
}

The loop begins by stating that the current item is to be assigned to a constant named index. The statement then declares a closed range operator to indicate that the for loop is to iterate through a range of numbers, starting at 1 and ending at 5. The body of the loop simply prints out a message to the console panel indicating the current value assigned to the index constant, resulting in the following output:

Value of index is 1
Value of index is 2
Value of index is 3
Value of index is 4
Value of index is 5

As will be demonstrated in the “Working with Array and Dictionary Collections in Swift” chapter of this book, the for-in loop is of particular benefit when working with collections such as arrays and dictionaries.

The declaration of a constant name in which to store a reference to the current item is not mandatory. In the event that a reference to the current item is not required in the body of the for loop, the constant name in the for loop declaration can be replaced by an underscore character. For example:

var count = 0
 
for _ in 1...5 {
    // No reference to the current value is required.
    count += 1
}

The while Loop

The Swift for loop described previously works well when it is known in advance how many times a particular task needs to be repeated in a program. There will, however, be instances where code needs to be repeated until a certain condition is met, with no way of knowing in advance how many repetitions are going to be needed to meet that criterion. To address this need, Swift provides the while loop.

Essentially, the while loop repeats a set of tasks while a specified condition is met. The while loop syntax is defined as follows:

while condition {
      // Swift statements go here
}

In the above syntax, condition is an expression that will return either true or false and the // Swift statements go here comment represents the code to be executed while the condition expression is true. For example:

var myCount = 0
 
while  myCount < 100 {
      myCount += 1
}

In the above example, the while expression will evaluate whether the myCount variable is less than 100. If it is already greater than 100, the code in the braces is skipped and the loop exits without performing any tasks.

If, on the other hand, myCount is not greater than 100 the code in the braces is executed and the loop returns to the while statement and repeats the evaluation of myCount. This process repeats until the value of myCount is greater than 100, at which point the loop exits.

The repeat … while loop

The repeat … while loop replaces the Swift 1.x do .. while loop. It is often helpful to think of the repeat … while loop as an inverted while loop. The while loop evaluates an expression before executing the code contained in the body of the loop. If the expression evaluates to false on the first check then the code is not executed. The repeat … while loop, on the other hand, is provided for situations where you know that the code contained in the body of the loop will always need to be executed at least once. For example, you may want to keep stepping through the items in an array until a specific item is found. You know that you have to at least check the first item in the array to have any hope of finding the entry you need. The syntax for the repeat … while loop is as follows:

repeat {
       // Swift statements here
} while conditional expression

In the repeat … while example below the loop will continue until the value of a variable named i equals 0:

var i = 10
 
repeat {
       i -= 1
} while (i > 0)

Breaking from Loops

Having created a loop, it is possible that under certain conditions you might want to break out of the loop before the completion criteria have been met (particularly if you have created an infinite loop). One such example might involve continually checking for activity on a network socket. Once activity has been detected it will most likely be necessary to break out of the monitoring loop and perform some other task.

For the purpose of breaking out of a loop, Swift provides the break statement which breaks out of the current loop and resumes execution at the code directly after the loop. For example:

var j = 10
 
for _ in 0 ..< 100
{
    j += j
 
    if j > 100 {
        break
    }
 
    print("j = \(j)")
}

In the above example the loop will continue to execute until the value of j exceeds 100 at which point the loop will exit and execution will continue with the next line of code after the loop.

The continue Statement

The continue statement causes all remaining code statements in a loop to be skipped, and execution to be returned to the top of the loop. In the following example, the print function is only called when the value of variable i is an even number:

var i = 1
 
while i < 20
{
        i += 1
 
        if (i % 2) != 0 {
            continue
        }
 
        print("i = \(i)")
}

The continue statement in the above example will cause the print call to be skipped unless the value of i can be divided by 2 with no remainder. If the continue statement is triggered, execution will skip to the top of the while loop and the statements in the body of the loop will be repeated (until the value of i exceeds 19).

Conditional Control Flow

In the previous chapter we looked at how to use logical expressions in Swift to determine whether something is true or false. Since programming is largely an exercise in applying logic, much of the art of programming involves writing code that makes decisions based on one or more criteria. Such decisions define which code gets executed and, conversely, which code gets by-passed when the program is executing.

Using the if Statement

The if statement is perhaps the most basic of control flow options available to the Swift programmer. Programmers who are familiar with C, Objective-C, C++ or Java will immediately be comfortable using Swift if statements. The basic syntax of the Swift if statement is as follows:

if boolean expression { 
    // Swift code to be performed when expression evaluates to true 
}

Unlike some other programming languages, it is important to note that the braces ({}) are mandatory in Swift, even if only one line of code is executed after the if expression.

Essentially if the Boolean expression evaluates to true then the code in the body of the statement is executed. The body of the statement is enclosed in braces ({}). If, on the other hand, the expression evaluates to false the code in the body of the statement is skipped.

For example, if a decision needs to be made depending on whether one value is greater than another, we would write code similar to the following:

let x = 10
 
if x > 9 {
      print("x is greater than 9!")
}

Clearly, x is indeed greater than 9 causing the message to appear in the console panel.

Using if … else … Statements

The next variation of the if statement allows us to also specify some code to perform if the expression in the if statement evaluates to false. The syntax for this construct is as follows:

if boolean expression { 
    // Code to be executed if expression is true 
} else { 
    // Code to be executed if expression is false 
}

Using the above syntax, we can now extend our previous example to display a different message if the comparison expression evaluates to be false:

let x = 10
 
if x > 9 {
         print("x is greater than 9!")
} else {
         print("x is less than 10!")
}

In this case, the second print statement would execute if the value of x was less than or equal to 9.

Using if … else if … Statements

So far, we have looked at if statements that make decisions based on the result of a single logical expression. Sometimes it becomes necessary to make decisions based on a number of different criteria. For this purpose, we can use the if … else if … construct, an example of which is as follows:

let x = 9
 
if x == 10 {
        print("x is 10")
} else if x == 9 {
        print("x is 9")
} else if x == 8 {
        print("x is 8")
}

This approach works well for a moderate number of comparisons but can become cumbersome for a larger volume of expression evaluations. For such situations, the Swift switch statement provides a more flexible and efficient solution. For more details on using the switch statement refer to the next chapter entitled“The Swift Switch Statement”.

The guard Statement

The guard statement is a Swift language feature introduced as part of Swift 2. A guard statement contains a Boolean expression which must evaluate to true in order for the code located after the guard statement to be executed. The guard statement must include an else clause to be executed in the event that the expression evaluates to false. The code in the else clause must contain a statement to exit the current code flow (i.e., a return, break, continue or throw statement). Alternatively, the else block may call any other function or method that does not itself return.

The syntax for the guard statement is as follows:

guard <boolean expressions> else {
   // code to be executed if expression is false
   <exit statement here>
} 
 
// code here is executed if expression is true

The guard statement essentially provides an “early exit” strategy from the current function or loop in the event that a specified requirement is not met.

The following code example implements a guard statement within a function:

func multiplyByTen(value: Int?) {
 
    guard let number = value, number < 10 else {
        print("Number is too high")
        return
    }
 
    let result = number * 10
    print(result)
}
 
multiplyByTen(value: 5)
multiplyByTen(value: 10)

The function takes as a parameter an integer value in the form of an optional. The guard statement uses optional binding to unwrap the value and verify that it is less than 10. In the event that the variable could not be unwrapped, or that its value is greater than 9, the else clause is triggered, the error message printed, and the return statement executed to exit the function.

If the optional contains a value less than 10, the code after the guard statement executes to multiply the value by 10 and print the result. A particularly important point to note about the above example is that the unwrapped number variable is available to the code outside of the guard statement. This would not have been the case had the variable been unwrapped using an if statement.

Summary

The term control flow is used to describe the logic that dictates the execution path that is taken through the source code of an application as it runs. This chapter has looked at the two types of control flow provided by Swift (looping and conditional) and explored the various Swift constructs that are available to implement both forms of control flow logic.

Swift Operators and Expressions

So far we have looked at using variables and constants in Swift and also described the different data types. Being able to create variables, however, is only part of the story. The next step is to learn how to use these variables and constants in Swift code. The primary method for working with data is in the form of expressions.

Expression Syntax in Swift

The most basic Swift expression consists of an operator, two operands and an assignment. The following is an example of an expression:

var myresult = 1 + 2

In the above example, the (+) operator is used to add two operands (1 and 2) together. The assignment operator (=) subsequently assigns the result of the addition to a variable named myresult. The operands could just have easily been variables (or a mixture of constants and variables) instead of the actual numerical values used in the example. In the remainder of this chapter we will look at the basic types of operators available in Swift.

The Basic Assignment Operator

We have already looked at the most basic of assignment operators, the = operator. This assignment operator simply assigns the result of an expression to a variable. In essence, the = assignment operator takes two operands. The left-hand operand is the variable or constant to which a value is to be assigned and the right-hand operand is the value to be assigned. The right-hand operand is, more often than not, an expression which performs some type of arithmetic or logical evaluation, the result of which will be assigned to the variable or constant. The following examples are all valid uses of the assignment operator:

var x: Int? // Declare an optional Int variable
var y = 10 // Declare and initialize a second Int variable
 
x = 10 // Assign a value to x
x = x! + y // Assign the result of x + y to x
x = y // Assign the value of y to x

Swift Arithmetic Operators

Swift provides a range of operators for the purpose of creating mathematical expressions. These operators primarily fall into the category of binary operators in that they take two operands. The exception is the unary negative operator (-) which serves to indicate that a value is negative rather than positive. This contrasts with the subtraction operator (-) which takes two operands (i.e., one value to be subtracted from another). For example:

var x = -10 // Unary - operator used to assign -10 to variable x
x = x - 5 // Subtraction operator. Subtracts 5 from x

The following table lists the primary Swift arithmetic operators:

Operator

Description

-(unary)

Negates the value of a variable or expression

*

Multiplication

/

Division

+

Addition

Subtraction

%

Remainder/Modulo

Table 6-1

Note that multiple operators may be used in a single expression. For example:

 x = y * 10 + z - 5 / 4

Compound Assignment Operators

In an earlier section we looked at the basic assignment operator (=). Swift provides a number of operators designed to combine an assignment with a mathematical or logical operation. These are primarily of use when performing an evaluation where the result is to be stored in one of the operands. For example, one might write an expression as follows:

x = x + y

The above expression adds the value contained in variable x to the value contained in variable y and stores the result in variable x. This can be simplified using the addition compound assignment operator:

x += y

The above expression performs exactly the same task as x = x + y but saves the programmer some typing.

Numerous compound assignment operators are available in Swift, the most frequently used of which are outlined in the following table:

Operator

Description

x += y

Add x to y and place result in x

x -= y

Subtract y from x and place result in x

x *= y

Multiply x by y and place result in x

x /= y

Divide x by y and place result in x

x %= y

Perform Modulo on x and y and place result in x

Table 6-2

Comparison Operators

Swift also includes a set of logical operators useful for performing comparisons. These operators all return a Boolean result depending on the result of the comparison. These operators are binary operators in that they work with two operands.

Comparison operators are most frequently used in constructing program flow control logic. For example, an if statement may be constructed based on whether one value matches another:

if x == y {
      // Perform task
}

The result of a comparison may also be stored in a Bool variable. For example, the following code will result in a true value being stored in the variable result:

var result: Bool?
var x = 10
var y = 20
 
result = x < y

Clearly 10 is less than 20, resulting in a true evaluation of the x < y expression. The following table lists the full set of Swift comparison operators:

Operator

Description

x == y

Returns true if x is equal to y

x > y

Returns true if x is greater than y

x >= y

Returns true if x is greater than or equal to y

x < y

Returns true if x is less than y

x <= y

Returns true if x is less than or equal to y

x != y

Returns true if x is not equal to y

Table 6-3

Boolean Logical Operators

Swift also provides a set of so-called logical operators designed to return Boolean true or false values. These operators both return Boolean results and take Boolean values as operands. The key operators are NOT (!), AND (&&) and OR (||).

The NOT (!) operator simply inverts the current value of a Boolean variable, or the result of an expression. For example, if a variable named flag is currently true, prefixing the variable with a ‘!’ character will invert the value to false:

var flag = true // variable is true
var secondFlag = !flag // secondFlag set to false

The OR (||) operator returns true if one of its two operands evaluates to true, otherwise it returns false. For example, the following code evaluates to true because at least one of the expressions either side of the OR operator is true:

if (10 < 20) || (20 < 10) {
        print("Expression is true")
}

The AND (&&) operator returns true only if both operands evaluate to be true. The following example will return false because only one of the two operand expressions evaluates to true:

if (10 < 20) && (20 < 10) {
      print("Expression is true")
}

Range Operators

Swift includes several useful operators that allow ranges of values to be declared. As will be seen in later chapters, these operators are invaluable when working with looping in program logic.

The syntax for the closed range operator is as follows:

x…y

This operator represents the range of numbers starting at x and ending at y where both x and y are included within the range. The range operator 5…8, for example, specifies the numbers 5, 6, 7 and 8. The half-open range operator, on the other hand uses the following syntax:

x..<y

In this instance, the operator encompasses all the numbers from x up to, but not including, y. A half-closed range operator 5..<8, therefore, specifies the numbers 5, 6 and 7.

Finally, the one-sided range operator specifies a range that can extend as far as possible in a specified range direction until the natural beginning or end of the range is reached (or until some other condition is met). A one-sided range is declared by omitting the number from one side of the range declaration, for example:

x…

or

…y

The previous chapter, for example, explained that a String in Swift is actually a collection of individual characters. A range to specify the characters in a string starting with the character at position 2 through to the last character in the string (regardless of string length) would be declared as follows:

2…

Similarly, to specify a range that begins with the first character and ends with the character at position 6, the range would be specified as follows:

…6

The Ternary Operator

Swift supports the ternary operator to provide a shortcut way of making decisions within code. The syntax of the ternary operator (also known as the conditional operator) is as follows:

condition ? true expression : false expression

The way the ternary operator works is that condition is replaced with an expression that will return either true or false. If the result is true then the expression that replaces the true expression is evaluated. Conversely, if the result was false then the false expression is evaluated. Let’s see this in action:

let x = 10
let y = 20
 
print("Largest number is \(x > y ? x : y)")

The above code example will evaluate whether x is greater than y. Clearly this will evaluate to false resulting in y being returned to the print call for display to the user:

Largest number is 20

Nil Coalescing Operator

The nil coalescing operator (??) allows a default value to be used in the event that an optional has a nil value. The following example will output text which reads “Welcome back, customer” because the customerName optional is set to nil:

let customerName: String? = nil
print("Welcome back, \(customerName ?? "customer")")

If, on the other hand, customerName is not nil, the optional will be unwrapped and the assigned value displayed:

let customerName: String? = "John"
print("Welcome back, \(customerName ?? "customer")")

On execution, the print statement output will now read “Welcome back, John”.

Bitwise Operators

As previously discussed, computer processors work in binary. These are essentially streams of ones and zeros, each one referred to as a bit. Bits are formed into groups of 8 to form bytes. As such, it is not surprising that we, as programmers, will occasionally end up working at this level in our code. To facilitate this requirement, Swift provides a range of bit operators.

Those familiar with bitwise operators in other languages such as C, C++, C#, Objective-C and Java will find nothing new in this area of the Swift language syntax. For those unfamiliar with binary numbers, now may be a good time to seek out reference materials on the subject in order to understand how ones and zeros are formed into bytes to form numbers. Other authors have done a much better job of describing the subject than we can do within the scope of this book.

For the purposes of this exercise we will be working with the binary representation of two numbers (for the sake of brevity we will be using 8-bit values in the following examples). First, the decimal number 171 is represented in binary as:

10101011

Second, the number 3 is represented by the following binary sequence:

00000011

Now that we have two binary numbers with which to work, we can begin to look at the Swift bitwise operators:

Bitwise NOT

The Bitwise NOT is represented by the tilde (~) character and has the effect of inverting all of the bits in a number. In other words, all the zeros become ones and all the ones become zeros. Taking our example 3 number, a Bitwise NOT operation has the following result:

00000011 NOT
========
11111100

The following Swift code, therefore, results in a value of -4:

let y = 3
let z = ~y
 
print("Result is \(z)")

Bitwise AND

The Bitwise AND is represented by a single ampersand (&). It makes a bit by bit comparison of two numbers. Any corresponding position in the binary sequence of each number where both bits are 1 results in a 1 appearing in the same position of the resulting number. If either bit position contains a 0 then a zero appears in the result. Taking our two example numbers, this would appear as follows:

10101011 AND
00000011
========
00000011

As we can see, the only locations where both numbers have 1s are the last two positions. If we perform this in Swift code, therefore, we should find that the result is 3 (00000011):

let x = 171
let y = 3
let z = x & y
 
print("Result is \(z)")

Bitwise OR

The bitwise OR also performs a bit by bit comparison of two binary sequences. Unlike the AND operation, the OR places a 1 in the result if there is a 1 in the first or second operand. The operator is represented by a single vertical bar character (|). Using our example numbers, the result will be as follows:

10101011 OR
00000011
========
10101011

If we perform this operation in a Swift example the result will be 171:

let x = 171
let y = 3
let z = x | y
 
print("Result is \(z)")

Bitwise XOR

The bitwise XOR (commonly referred to as exclusive OR and represented by the caret ‘^’ character) performs a similar task to the OR operation except that a 1 is placed in the result if one or other corresponding bit positions in the two numbers is 1. If both positions are a 1 or a 0 then the corresponding bit in the result is set to a 0. For example:

10101011 XOR
00000011
========
10101000

The result in this case is 10101000 which converts to 168 in decimal. To verify this we can, once again, try some Swift code:

let x = 171
let y = 3
let z = x ^ y
 
print("Result is \(z)")

Bitwise Left Shift

The bitwise left shift moves each bit in a binary number a specified number of positions to the left. Shifting an integer one position to the left has the effect of doubling the value.

As the bits are shifted to the left, zeros are placed in the vacated right most (low order) positions. Note also that once the left most (high order) bits are shifted beyond the size of the variable containing the value, those high order bits are discarded:

10101011 Left Shift one bit
========
101010110

In Swift the bitwise left shift operator is represented by the ‘<<’ sequence, followed by the number of bit positions to be shifted. For example, to shift left by 1 bit:

let x = 171
let z = x << 1
 
print("Result is \(z)")

When compiled and executed, the above code will display a message stating that the result is 342 which, when converted to binary, equates to 101010110.

Bitwise Right Shift

A bitwise right shift is, as you might expect, the same as a left except that the shift takes place in the opposite direction. Shifting an integer one position to the right has the effect of halving the value.

Note that since we are shifting to the right there is no opportunity to retain the lower most bits regardless of the data type used to contain the result. As a result, the low order bits are discarded. Whether or not the vacated high order bit positions are replaced with zeros or ones depends on whether the sign bit used to indicate positive and negative numbers is set or not.

10101011 Right Shift one bit
========
01010101

The bitwise right shift is represented by the ‘>>’ character sequence followed by the shift count:

let x = 171
let z = x >> 1
 
print("Result is \(z)")

When executed, the above code will report the result of the shift as being 85, which equates to binary 01010101.

Compound Bitwise Operators

As with the arithmetic operators, each bitwise operator has a corresponding compound operator that allows the operation and assignment to be performed using a single operator:

Operator

Description

x &= y

Perform a bitwise AND of x and y and assign result to x

x |= y

Perform a bitwise OR of x and y and assign result to x

x ^= y

Perform a bitwise XOR of x and y and assign result to x

x <<= n

Shift x left by n places and assign result to x

x >>= n

Shift x right by n places and assign result to x

Table 6-4

Summary

Operators and expressions provide the underlying mechanism by which variables and constants are manipulated and evaluated within Swift code. This can take the simplest of forms whereby two numbers are added using the addition operator in an expression and the result stored in a variable using the assignment operator. Operators fall into a range of categories, details of which have been covered in this chapter.

Swift Data Types, Constants and Variables

If you are new to the Swift programming language then the next few chapters are recommended reading. Although SwiftUI makes the development of apps easier, it will still be necessary to learn Swift programming both to understand SwiftUI and develop fully functional apps.

If, on the other hand, you are familiar with the Swift programming language you can skip the Swift specific chapters that follow (though if you are not familiar with implicit returns from single expressions, opaque return types and property wrappers you should at least read the sections and chapters relating to these features before moving on to the SwiftUI chapters).

Prior to the introduction of iOS 8, the stipulated programming language for the development of iOS applications was Objective-C. When Apple announced iOS 8, however, the company also introduced an alternative to Objective-C in the form of the Swift programming language.

Due entirely to the popularity of iOS, Objective-C had become one of the more widely used programming languages. With origins firmly rooted in the 40-year-old C Programming Language, however, and despite recent efforts to modernize some aspects of the language syntax, Objective-C was beginning to show its age.

Swift, on the other hand, is a relatively new programming language designed specifically to make programming easier, faster and less prone to programmer error. Starting with a clean slate and no burden of legacy, Swift is a new and innovative language with which to develop applications for iOS, iPadOS, macOS, watchOS and tvOS with the advantage that much of the syntax will be familiar to those with experience of other programming languages.

The next several chapters will provide an overview and introduction to Swift programming. The intention of these chapters is to provide enough information so that you can begin to confidently program using Swift. For an exhaustive and in-depth guide to all the features, intricacies and capabilities of Swift, some time spent reading Apple’s excellent book entitled “The Swift Programming Language” (available free of charge from within the Apple Books app) is strongly recommended.

Using a Swift Playground

Both this and the following few chapters are intended to introduce the basics of the Swift programming language. As outlined in the previous chapter, entitled “An Introduction to Xcode 14 Playgrounds” the best way to learn Swift is to experiment within a Swift playground environment. Before starting this chapter, therefore, create a new playground and use it to try out the code in both this and the other Swift introduction chapters that follow.

Swift Data Types

When we look at the different types of software that run on computer systems and mobile devices, from financial applications to graphics intensive games, it is easy to forget that computers are really just binary machines. Binary systems work in terms of 0 and 1, true or false, set and unset. All the data sitting in RAM, stored on disk drives and flowing through circuit boards and buses are nothing more than sequences of 1s and 0s. Each 1 or 0 is referred to as a bit and bits are grouped together in blocks of 8, each group being referred to as a byte. When people talk about 32-bit and 64-bit computer systems they are talking about the number of bits that can be handled simultaneously by the CPU bus. A 64-bit CPU, for example, is able to handle data in 64-bit blocks, resulting in faster performance than a 32-bit based system.

Humans, of course, don’t think in binary. We work with decimal numbers, letters and words. In order for a human to easily (easily being a subjective term in this context) program a computer, some middle ground between human and computer thinking is needed. This is where programming languages such as Swift come into play. Programming languages allow humans to express instructions to a computer in terms and structures we understand and then compile that down to a format that can be executed by a CPU.

One of the fundamentals of any program involves data, and programming languages such as Swift define a set of data types that allow us to work with data in a format we understand when programming. For example, if we want to store a number in a Swift program, we could do so with syntax similar to the following:

var mynumber = 10

In the above example, we have created a variable named mynumber and then assigned to it the value of 10. When we compile the source code down to the machine code used by the CPU, the number 10 is seen by the computer in binary as:

1010

Now that we have a basic understanding of the concept of data types and why they are necessary we can take a closer look at some of the more commonly used data types supported by Swift.

Integer Data Types

Swift integer data types are used to store whole numbers (in other words a number with no decimal places). Integers can be signed (capable of storing positive, negative and zero values) or unsigned (positive and zero values only).

Swift provides support for 8, 16, 32 and 64-bit integers (represented by the Int8, Int16, Int32 and Int64 types respectively). The same variants are also available for unsigned integers (UInt8, UInt16, UInt32 and UInt64).

In general, Apple recommends using the Int data type rather than one of the above specifically sized data types. The Int data type will use the appropriate integer size for the platform on which the code is running.

All integer data types contain bounds properties which can be accessed to identify the minimum and maximum supported values of that particular type. The following code, for example, outputs the minimum and maximum bounds for the 32-bit signed integer data type:

print("Int32 Min = \(Int32.min) Int32 Max = \(Int32.max)")

When executed, the above code will generate the following output:

Int32 Min = -2147483648 Int32 Max = 2147483647

Floating Point Data Types

The Swift floating point data types are able to store values containing decimal places. For example, 4353.1223 would be stored in a floating-point data type. Swift provides two floating point data types in the form of Float and Double. Which type to use depends on the size of value to be stored and the level of precision required. The Double type can be used to store up to 64-bit floating point numbers with a level of precision of 15 decimal places or greater. The Float data type, on the other hand, is limited to 32-bit floating point numbers and offers a level of precision as low as 6 decimal places depending on the native platform on which the code is running. Alternatively, the Float16 type may be used to store 16-bit floating point values. Float16 provides greater performance at the expense of lower precision.

Bool Data Type

Swift, like other languages, includes a data type for the purpose of handling true or false (1 or 0) conditions. Two Boolean constant values (true and false) are provided by Swift specifically for working with Boolean data types.

Character Data Type

The Swift Character data type is used to store a single character of rendered text such as a letter, numerical digit, punctuation mark or symbol. Internally characters in Swift are stored in the form of grapheme clusters. A grapheme cluster is made of two or more Unicode scalars that are combined to represent a single visible character.

The following lines assign a variety of different characters to Character type variables:

var myChar1 = "f" 
var myChar2 = ":" 
var myChar3 = "X"

Characters may also be referenced using Unicode code points. The following example assigns the ‘X’ character to a variable using Unicode:

var myChar4 = "\u{0058}"

String Data Type

The String data type is a sequence of characters that typically make up a word or sentence. In addition to providing a storage mechanism, the String data type also includes a range of string manipulation features allowing strings to be searched, matched, concatenated and modified. Strings in Swift are represented internally as collections of characters (where a character is, as previously discussed, comprised of one or more Unicode scalar values).

Strings can also be constructed using combinations of strings, variables, constants, expressions, and function calls using a concept referred to as string interpolation. For example, the following code creates a new string from a variety of different sources using string interpolation before outputting it to the console:

var userName = "John"
var inboxCount = 25
let maxCount = 100
 
var message = "\(userName) has \(inboxCount) messages. Message capacity remaining is \(maxCount - inboxCount) messages."
 
print(message)

When executed, the code will output the following message:

John has 25 messages. Message capacity remaining is 75 messages.

A multiline string literal may be declared by encapsulating the string within triple quotes as follows:

var multiline = """
 
    The console glowed with flashing warnings.
    Clearly time was running out.
 
    "I thought you said you knew how to fly this!" yelled Mary.
 
    "It was much easier on the simulator" replied her brother,
     trying to keep the panic out of his voice.
 
"""
 
print(multiline)

The above code will generate the following output when run:

    The console glowed with flashing warnings.
    Clearly time was running out.
 
    "I thought you said you knew how to fly this!" yelled Mary.
 
    "It was much easier on the simulator" replied her brother,
    trying to keep the panic out of his voice.

The amount by which each line is indented within a multiline literal is calculated as the number of characters by which the line is indented minus the number of characters by which the closing triple quote line is indented. If, for example, the fourth line in the above example had a 10-character indentation and the closing triple quote was indented by 5 characters, the actual indentation of the fourth line within the string would be 5 characters. This allows multiline literals to be formatted tidily within Swift code while still allowing control over the indentation of individual lines.

5.2.6 Special Characters/Escape Sequences

In addition to the standard set of characters outlined above, there is also a range of special characters (also referred to as escape sequences) available for specifying items such as a new line, tab or a specific Unicode value within a string. These special characters are identified by prefixing the character with a backslash (a concept referred to as escaping). For example, the following assigns a new line to the variable named newline:

var newline = "\n"

In essence, any character that is preceded by a backslash is considered to be a special character and is treated accordingly. This raises the question as to what to do if you actually want a backslash character. This is achieved by escaping the backslash itself:

var backslash = "\\"

Commonly used special characters supported by Swift are as follows:

  • \n – New line
  • \r – Carriage return
  • \t – Horizontal tab
  • \\ – Backslash
  • \” – Double quote (used when placing a double quote into a string declaration)
  • \’ – Single quote (used when placing a single quote into a string declaration)
  • \u{nn} – Single byte Unicode scalar where nn is replaced by two hexadecimal digits representing the Unicode character.
  • \u{nnnn} – Double byte Unicode scalar where nnnn is replaced by four hexadecimal digits representing the Unicode character.
  • \u{nnnnnnnn} – Four-byte Unicode scalar where nnnnnnnn is replaced by eight hexadecimal digits representing the Unicode character.

Swift Variables

Variables are essentially locations in computer memory reserved for storing the data used by an application. Each variable is given a name by the programmer and assigned a value. The name assigned to the variable may then be used in the Swift code to access the value assigned to that variable. This access can involve either reading the value of the variable or changing the value. It is, of course, the ability to change the value of variables, which gives them the name variable.

Swift Constants

A constant is like a variable in that it provides a named location in memory to store a data value. Constants differ in one significant way in that once a value has been assigned to a constant it cannot subsequently be changed.

Constants are particularly useful if a value is used repeatedly throughout the application code. Rather than use the value each time, it makes the code easier to read if the value is first assigned to a constant which is then referenced in the code. For example, it might not be clear to someone reading your Swift code why you used the value 5 in an expression. If, instead of the value 5, you use a constant named interestRate the purpose of the value becomes much clearer. Constants also have the advantage that if the programmer needs to change a widely used value, it only needs to be changed once in the constant declaration and not each time it is referenced.

As with variables, constants have a type, a name and a value. Unlike variables, however, once a value has been assigned to a constant, that value cannot subsequently be changed.

Declaring Constants and Variables

Variables are declared using the var keyword and may be initialized with a value at creation time. If the variable is declared without an initial value, it must be declared as being optional (a topic that will be covered later in this chapter). The following, for example, is a typical variable declaration:

var userCount = 10

Constants are declared using the let keyword.

let maxUserCount = 20

For greater code efficiency and execution performance, Apple recommends using constants rather than variables whenever possible.

Type Annotations and Type Inference

Swift is categorized as a type safe programming language. This essentially means that once the data type of a variable has been identified, that variable cannot subsequently be used to store data of any other type without inducing a compilation error. This contrasts to loosely typed programming languages where a variable, once declared, can subsequently be used to store other data types.

There are two ways in which the type of a constant or variable will be identified. One approach is to use a type annotation at the point the variable or constant is declared in the code. This is achieved by placing a colon after the constant or variable name followed by the type declaration. The following line of code, for example, declares a variable named userCount as being of type Int:

var userCount: Int = 10

In the absence of a type annotation in a declaration, the Swift compiler uses a technique referred to as type inference to identify the type of the constant or variable. When relying on type inference, the compiler looks to see what type of value is being assigned to the constant or variable at the point that it is initialized and uses that as the type. Consider, for example, the following variable and constant declarations:

var signalStrength = 2.231
let companyName = "My Company"

During compilation of the above lines of code, Swift will infer that the signalStrength variable is of type Double (type inference in Swift defaults to Double for all floating-point numbers) and that the companyName constant is of type String.

When a constant is declared without a type annotation it must be assigned a value at the point of declaration:

let bookTitle = "SwiftUI Essentials"

If a type annotation is used when the constant is declared, however, the value can be assigned later in the code. For example:

let bookTitle: String
.
.
if iosBookType {
    bookTitle = "SwiftUI Essentials"
} else {
    bookTitle = "Android Studio Development Essentials"
}

It is important to note that a value may only be assigned to a constant once. A second attempt to assign a value to a constant will result in a syntax error.

The Swift Tuple

Before proceeding, now is a good time to introduce the Swift tuple. The tuple is perhaps one of the simplest, yet most powerful features of the Swift programming language. A tuple is, quite simply, a way to temporarily group together multiple values into a single entity. The items stored in a tuple can be of any type and there are no restrictions requiring that those values all be of the same type. A tuple could, for example, be constructed to contain an Int value, a Double value and a String as follows:

let myTuple = (10, 432.433, "This is a String")

The elements of a tuple can be accessed using a number of different techniques. A specific tuple value can be accessed simply by referencing the index position (with the first value being at index position 0). The code below, for example, extracts the string resource (at index position 2 in the tuple) and assigns it to a new string variable:

let myTuple = (10, 432.433, "This is a String") let myString = myTuple.2 print(myString)

Alternatively, all the values in a tuple may be extracted and assigned to variables or constants in a single statement:

let (myInt, myFloat, myString) = myTuple

This same technique can be used to extract selected values from a tuple while ignoring others by replacing the values to be ignored with an underscore character. The following code fragment extracts the integer and string values from the tuple and assigns them to variables, but ignores the floating-point value:

var (myInt, _, myString) = myTuple

When creating a tuple, it is also possible to assign a name to each value:

let myTuple = (count: 10, length: 432.433, message: "This is a String")

The names assigned to the values stored in a tuple may then be used to reference those values in code. For example, to output the message string value from the myTuple instance, the following line of code could be used:

print(myTuple.message)

Perhaps the most powerful use of tuples is, as will be seen in later chapters, the ability to return multiple values from a function.

The Swift Optional Type

The Swift optional data type is a new concept that does not exist in most other programming languages. The purpose of the optional type is to provide a safe and consistent approach to handling situations where a variable or constant may not have any value assigned to it.

Variables are declared as being optional by placing a ? character after the type declaration. The following code declares an optional Int variable named index:

var index: Int?

The variable index can now either have an integer value assigned to it or have nothing assigned to it. Behind the scenes, and as far as the compiler and runtime are concerned, an optional with no value assigned to it actually has a value of nil.

An optional can easily be tested (typically using an if statement) to identify whether it has a value assigned to it as follows:

var index: Int?
 
if index != nil {
    // index variable has a value assigned to it
} else {
    // index variable has no value assigned to it
}

If an optional has a value assigned to it, that value is said to be “wrapped” within the optional. The value wrapped in an optional may be accessed using a concept referred to as forced unwrapping. This simply means that the underlying value is extracted from the optional data type, a procedure that is performed by placing an exclamation mark (!) after the optional name.

To explore this concept of unwrapping optional types in more detail, consider the following code:

var index: Int?
 
index = 3
 
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
 
if index != nil {
    print(treeArray[index!])
} else {
    print("index does not contain a value")
}

The code simply uses an optional variable to hold the index into an array of strings representing the names of tree species (Swift arrays will be covered in more detail in the chapter entitled “Working with Array and Dictionary Collections in Swift”). If the index optional variable has a value assigned to it, the tree name at that location in the array is printed to the console. Since the index is an optional type, the value has been unwrapped by placing an exclamation mark after the variable name:

print(treeArray[index!])

Had the index not been unwrapped (in other words the exclamation mark omitted from the above line), the compiler would have issued an error similar to the following:

Value of optional type ‘Int?’ must be unwrapped to a value of type ‘Int’

As an alternative to forced unwrapping, the value assigned to an optional may be allocated to a temporary variable or constant using optional binding, the syntax for which is as follows:

if let constantname = optionalName {
 
}
 
if var variablename = optionalName {
 
}

The above constructs perform two tasks. In the first instance, the statement ascertains whether the designated optional contains a value. Second, in the event that the optional has a value, that value is assigned to the declared constant or variable and the code within the body of the statement is executed. The previous forced unwrapping example could, therefore, be modified as follows to use optional binding instead:

var index: Int?
 
index = 3
 
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
 
if let myvalue = index {
    print(treeArray[myvalue])
} else {
    print("index does not contain a value")
}

In this case the value assigned to the index variable is unwrapped and assigned to a temporary (also referred to as shadow) constant named myvalue which is then used as the index reference into the array. Note that the myvalue constant is described as temporary since it is only available within the scope of the if statement. Once the if statement completes execution, the constant will no longer exist. For this reason, there is no conflict in using the same temporary name as that assigned to the optional. The following is, for example, valid code:

.
.
if let index = index {
    print(treeArray[index])
} else {
.
.

When considering the above example, the use of the temporary value begins to seem redundant. Fortunately, the Swift development team arrived at the same conclusion and introduced the following shorthand if-let syntax in Swift 5.7:

var index: Int?
 
index = 3
 
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
 
if let index {
    print(treeArray[index])
} else {
    print("index does not contain a value")
}

Using this approach it is no longer necessary to assign the optional to a temporary value.

Optional binding may also be used to unwrap multiple optionals and include a Boolean test condition, the syntax for which is as follows:

if let constname1 = optName1, let constname2 = optName2, 
     let optName3 = …, <boolean statement> {
 
}

The shorthand if-let syntax is also available when working with multiple optionals and test conditions avoiding the need to use temporary values:

if let constname1, let constname2, 
     let optName3, ... <boolean statement> {
 
}

The following code, for example, uses shorthand optional binding to unwrap two optionals within a single statement:

var pet1: String?
var pet2: String?
 
pet1 = "cat"
pet2 = "dog"
 
if let pet1, let pet2 {
    print(pet1)
    print(pet2)
} else {
    print("insufficient pets")
}

The code fragment below, on the other hand, also makes use of the Boolean test clause condition:

if let pet1, let pet2, petCount > 1 {
    print(pet1)
    print(pet2)
} else {
    print("insufficient pets")
}

In the above example, the optional binding will not be attempted unless the value assigned to petCount is greater than 1.

It is also possible to declare an optional as being implicitly unwrapped. When an optional is declared in this way, the underlying value can be accessed without having to perform forced unwrapping or optional binding. An optional is declared as being implicitly unwrapped by replacing the question mark (?) with an exclamation mark (!) in the declaration. For example:

var index: Int! // Optional is now implicitly unwrapped
 
index = 3
 
var treeArray = ["Oak", "Pine", "Yew", "Birch"]
 
if index != nil {
    print(treeArray[index]) 
} else {
    print("index does not contain a value")
}

With the index optional variable now declared as being implicitly unwrapped, it is no longer necessary to unwrap the value when it is used as an index into the array in the above print call.

One final observation with regard to optionals in Swift is that only optional types are able to have no value or a value of nil assigned to them. In Swift it is not, therefore, possible to assign a nil value to a non-optional variable or constant. The following declarations, for instance, will all result in errors from the compiler since none of the variables are declared as optional:

var myInt = nil // Invalid code
var myString: String = nil // Invalid Code
let myConstant = nil // Invalid code

Type Casting and Type Checking

When writing Swift code, situations will occur where the compiler is unable to identify the specific type of a value. This is often the case when a value of ambiguous or unexpected type is returned from a method or function call. In this situation it may be necessary to let the compiler know the type of value that your code is expecting or requires using the as keyword (a concept referred to as type casting).

The following code, for example, lets the compiler know that the value returned from the object(forKey:) method needs to be treated as a String type:

let myValue = record.object(forKey: "comment") as! String

In fact, there are two types of casting which are referred to as upcasting and downcasting. Upcasting occurs when an object of a particular class is cast to one of its superclasses. Upcasting is performed using the as keyword and is also referred to as guaranteed conversion since the compiler can tell from the code that the cast will be successful. The UIButton class, for example, is a subclass of the UIControl class as shown in the fragment of the UIKit class hierarchy shown in Figure 5-1:

Figure 5-1

Since UIButton is a subclass of UIControl, the object can be safely upcast as follows:

let myButton: UIButton = UIButton()
 
let myControl = myButton as UIControl 

Downcasting, on the other hand, occurs when a conversion is made from one class to another where there is no guarantee that the cast can be made safely or that an invalid casting attempt will be caught by the compiler. When an invalid cast is made in downcasting and not identified by the compiler it will most likely lead to an error at runtime.

Downcasting usually involves converting from a class to one of its subclasses. Downcasting is performed using the as! keyword syntax and is also referred to as forced conversion. Consider, for example, the UIKit UIScrollView class which has as subclasses both the UITableView and UITextView classes as shown in Figure 5-2:

Figure 5-2

In order to convert a UIScrollView object to a UITextView class a downcast operation needs to be performed. The following code attempts to downcast a UIScrollView object to UITextView using the guaranteed conversion or upcast approach:

let myScrollView: UIScrollView = UIScrollView()
 
let myTextView = myScrollView as UITextView

The above code will result in the following error:

‘UIScrollView’ is not convertible to ‘UITextView’

The compiler is indicating that a UIScrollView instance cannot be safely converted to a UITextView class instance. This does not necessarily mean that it is incorrect to do so, the compiler is simply stating that it cannot guarantee the safety of the conversion for you. The downcast conversion could instead be forced using the as! annotation:

let myTextView = myScrollView as! UITextView

Now the code will compile without an error. As an example of the dangers of downcasting, however, the above code will crash on execution stating that UIScrollView cannot be cast to UITextView. Forced downcasting should, therefore, be used with caution.

A safer approach to downcasting is to perform an optional binding using as?. If the conversion is performed successfully, an optional value of the specified type is returned, otherwise the optional value will be nil:

if let myTextView = myScrollView as? UITextView {
    print("Type cast to UITextView succeeded")
} else {
    print("Type cast to UITextView failed")
}

It is also possible to type check a value using the is keyword. The following code, for example, checks that a specific object is an instance of a class named MyClass:

if myobject is MyClass {
    // myobject is an instance of MyClass
}

Summary

This chapter has begun the introduction to Swift by exploring data types together with an overview of how to declare constants and variables. The chapter has also introduced concepts such as type safety, type inference, and optionals, each of which is an integral part of Swift programming and designed specifically to make code writing less prone to error.

Introduction to SwiftUI Essentials

The goal of this book is to teach the skills necessary to build iOS 15 applications using SwiftUI, Xcode 13, and the Swift 5.5 programming language.

Beginning with the basics, this book provides an outline of the steps necessary to set up an iOS development environment together with an introduction to the use of Swift Playgrounds to learn and experiment with Swift.

The book also includes in-depth chapters introducing the Swift 5.5 programming language including data types, control flow, functions, object-oriented programming, property wrappers, structured concurrency, and error handling.

An introduction to the key concepts of SwiftUI and project architecture is followed by a guided tour of Xcode in SwiftUI development mode. The book also covers the creation of custom SwiftUI views and explains how these views are combined to create user interface layouts including the use of stacks, frames, and forms.

Other topics covered include data handling using state properties in addition to observable, state, and environment objects, as are the key user interface design concepts such as modifiers, lists, tabbed views, context menus, user interface navigation, and outline groups.

The book also includes chapters covering graphics drawing, user interface animation, view transitions and gesture handling, WidgetKit, document-based apps, Core Data, CloudKit, and SiriKit integration.

Chapters are also provided explaining how to integrate SwiftUI views into existing UIKit-based projects and explains the integration of UIKit code into SwiftUI.

Finally, the book explains how to package up a completed app and upload it to the App Store for publication.

Along the way, the topics covered in the book are put into practice through detailed tutorials, the source code for which is also available for download.

The aim of this book, therefore, is to teach you the skills necessary to build your own apps for iOS 15 using SwiftUI. Assuming you are ready to download the iOS 15 SDK and Xcode 13 and have an Apple Mac system you are ready to get started.

For Swift Programmers

This book has been designed to address the needs of both existing Swift programmers and those who are new to both Swift and iOS app development. If you are familiar with the Swift 5.5 programming language, you can probably skip the Swift-specific chapters. If you are not yet familiar with the SwiftUI-specific language features of Swift, however, we recommend that you at least read the sections covering implicit returns from single expressions, opaque return types, and property wrappers. These features are central to the implementation and understanding of SwiftUI.

For Non-Swift Programmers

If you are new to programming in Swift then the entire book is appropriate for you. Just start at the beginning and keep going.

Source Code Download

The source code and Xcode project files for the examples contained in this book are available for download at:

https://www.ebookfrenzy.com/web/swiftui-ios15/

Errata

While we make every effort to ensure the accuracy of the content of this book, it is inevitable that a book covering a subject area of this size and complexity may include some errors and oversights. Any known issues with the book will be outlined, together with solutions at the following URL:

https://www.ebookfrenzy.com/errata/swiftui-ios15.html

In the event that you find an error not listed in the errata, please let us know by emailing our technical support team at [email protected].

SwiftUI Essentials – iOS 16 Edition

  1. Introduction to SwiftUI Essentials
  2. Swift Data Types, Constants and Variables
  3. Swift Operators and Expressions
  4. Swift Control Flow
  5. The Swift Switch Statement
  6. Swift Functions, Methods, and Closures
  7. The Basics of Swift Object-Oriented Programming
  8. An Introduction to Swift Subclassing and Extensions
  9. An Introduction to Swift Structures
  10. An Introduction to Swift Property Wrappers
  11. Working with Array and Dictionary Collections in Swift
  12. Understanding Error Handling in Swift
  13. An Overview of SwiftUI
  14. Using Xcode in SwiftUI Mode
  15. SwiftUI Architecture
  16. The Anatomy of a Basic SwiftUI Project
  17. Creating Custom Views with SwiftUI
  18. SwiftUI Stacks and Frames
  19. Working with SwiftUI State, Observable and Environment Objects
  20. A SwiftUI Example Tutorial
  21. An Overview of Swift Structured Concurrency
  22. An Introduction to Swift Actors
  23. SwiftUI Lifecycle Event Modifiers
  24. SwiftUI Observable and Environment Objects – A Tutorial
  25. SwiftUI Data Persistence using AppStorage and SceneStorage
  26. SwiftUI Stack Alignment and Alignment Guides
  27. SwiftUI Lists and Navigation
  28. A SwiftUI NavigationStack Tutorial
  29. Multicolumn Navigation in SwiftUI with NavigationSplitView
  30. A SwiftUI NavigationSplitView Tutorial
  31. An Overview of SwiftUI List, OutlineGroup and DisclosureGroup
  32. A SwiftUI List, OutlineGroup and DisclosureGroup Tutorial
  33. Building SwiftUI Grids with LazyVGrid and LazyHGrid
  34. A SwiftUI Grid and GridRow Tutorial
  35. Building Tabbed Views in SwiftUI
  36. Building Context Menus in SwiftUI
  37. Basic SwiftUI Graphics Drawing
  38. SwiftUI Animation and Transitions
  39. Working with Gesture Recognizers in SwiftUI
  40. Creating a Customized SwiftUI ProgressView
  41. An Overview of SwiftUI Charts
  42. A SwiftUI Charts Tutorial
  43. An Overview of SwiftUI DocumentGroup Scenes
  44. A SwiftUI DocumentGroup Tutorial
  45. An Introduction to Core Data and SwiftUI
  46. A SwiftUI Core Data Tutorial
  47. An Overview of SwiftUI Core Data and CloudKit Storage
  48. A SwiftUI Core Data and CloudKit Tutorial
  49. An Introduction to SwiftUI and SiriKit
  50. A SwiftUI SiriKit Tutorial
  51. Customizing the SiriKit Intent User Interface
  52. A SwiftUI SiriKit NSUserActivity Tutorial
  53. An Overview of SwiftUI Siri Shortcut Integration
  54. A SwiftUI Siri Shortcut Tutorial
  55. Building Widgets with SwiftUI and WidgetKit
  56. A SwiftUI WidgetKit Tutorial
  57. Supporting WidgetKit Size Families in SwiftUI
  58. A SwiftUI WidgetKit Deep Link Tutorial
  59. Adding Configuration Options to a WidgetKit Widget
  60. Integrating UIViews with SwiftUI
  61. Integrating UIViewControllers with SwiftUI
  62. Integrating SwiftUI with UIKit