Want me to personally work with you? You can hire me for one-on-one here.

Swift 3 Tutorial – Fundamentals

 

Swift 3 Tutorial

Swift 3 Tutorial

In this Swift 3 tutorial, we’ll focus on how beginners may approach going from complete beginner to having a basic grasp on Swift, and we’ll be working with Swift 3. We chose to write this tutorial because newcomers will find many tutorials out there that are out of date, so it’s not appropriate to simply write a “what’s new in Swift 3” post. Instead we’ll approach this from the perspective of someone who has some programming experience in at least one other language, and we’ll teach you what’s unique about Swift, and how to use it effectively. Ready? Let’s go!


Constants and Variables

Any Swift variable is either a constant or not. Not to be confused with the type of the variable, such as Float or Int, constants and variables are just ways to describe terms that hold a value which can change (are mutable), and constants that can not be changed (becuase they are immutable).

To define a constant, use the let keyword.

Example:

let name = "Jameson"

If you were to try and change the value of name, you would be unable to do so, and the Swift compiler would produce an error.

let name = "Jameson"
name = "Bob"
error: cannot assign to value: 'name' is a 'let' constant
name = "Bob"
~~~~ ^

On the other hand, by using the var keyword, we define a variable that can change:

var name = "Jameson"
name = "Bob"

This code does not produce an error.

In general, you should always default to using the let keyword, unless you know you need a var keyword. This leads to code that is ultimately safer. If you define a constant, and later attempt to modify it, you will get an error and at that time can determine if you should switch to use the var keyword, or if the error is giving you a hint that maybe you should rethink the current logic flow. In general, immutability is preferred over mutability; it simply leads to less programmers errors and makes it easier to reason about your code.


Basic Types

In Swift, a type is indicated by declaring a variable, then putting a colon, folowed by the type name. For example to declare an integer, which is of Swift type Int, you could use the following:

let age: Int = 5

Or similarly, if you want to declare a string:

let name: String = "Jameson"

Swift supports type inference, so you can usually omit the type information, and let the compiler determine what the type should be based on it’s initial value.

let age = 5
let name = "Jameson"

The types for age and name are still Int and String respectively, but we can skip the type annotation, because it is obvious that 5 is an Int, and "Jameson" is a String.

Remember, the let keyword simply makes these values constant. If we expected the value of age to change, but not name, we might write these declarations like so:

var age = 5
let name = "Jameson"

Now if we want to update the age value, it’s possible to do:

var age = 5
let name = "Jameson"
age = 25
print(age)
25

Working with Strings

It’s frequently useful to print a command to the console or otherwise evaluate a String containing other variables. For example, I might want to form a sentence with my variables age and name and print it to the console. I can do this using the + operator between each String.

let age = "15"
let name = "Robb"
 
let sentence = name + " is " + age
print(sentence)
Robb is 15

A shortcut for this is to write your String as you normally would without the + operator seperating each string, and put each variable inside of a set of parentheses, preceeded by a backslash \.

let sentence = "\(name) is \(age)"
print(sentence)
Robb is 15

As you can see this has the same effect, but is much easier to read and compose sentences.

One thing you may have noticed is that age is now of type String because it was assigned the value "15" instead of just 15 without the quotes. This is because concatenating a String and an Int will not automatically cast the Int to String, which is a neccessary step before concatenating is possible.

Or in other words, this code will produce an error:

let age = 15
let name = "Robb"
 
let sentence = name + " is " + age
print(sentence)
Error: Binary operator '+' cannot be applied to operands of type 'String' and 'Int'

So what we have to do is get age converted to a String before using it here. Thing is done by casting the variable. Simply call the String constructor and pass in the Int value:

let age = 15
let name = "Robb"
 
let stringAge = String(age)
 
let sentence = name + " is " + stringAge
print(sentence)
Robb is 15

We created a new variable called stringAge here, but we also could have performed the cast in-place, because the string interpolation will evaluate each expression separately, and the same goes for the contents of the parenthesis when interpolating strings that way:

let age = 15
let name = "Robb"
 
let sentence = name + " is " + String(age)
print(sentence)
print("\(name) enjoys being \(String(age))")
Robb is 15
Robb enjoys being
15

Optionals

In Swift, there is also the concept of the optional. An optional is just a variable that can be nil, null, or otherwise not set to any value. In general, you can think of any variable in most other programming languages as being an optional. The “optionality” of a variable is declared by appending a question mark (?) on to the end of the type name in a type annotation. So continuing the example above, where we know age and name will always be set, we might want to add another variable that could be nil, because it is possible that it just isn’t present. Let’s use favoriteColor as an example. Many people have a favorite color, but it’s possible someone doesn’t, or we just don’t have the data. We would declare this variable as an optional, and not assign it to any value.

var favoriteColor: String?

Implicit in the declaration of an optional with no value set, is the assignment to nil. We can verify this by examining the value of favoriteColor after declaring it as an optional by printing it to the console using the print() function.

var favoriteColor: String?
print(favoriteColor)
nil

We can later assign something to favoriteColor and see that it is no longer nil.

var favoriteColor: String?
favoriteColor = "Blue"
print(favoriteColor)
Optional("Blue")

Note that instead of just getting the string "Blue", we get Optional("Blue"). This is because the value is still “wrapped” inside of the optional.

You can think of optionals like a birthday gift. The box the gift comes in, wrapped up in some paper with pretty balloons on it, could actually be empty. A rather cruel gift to give someone on their birthday, but it’s possible to do. It could also have something inside of it. But either way, if we pick it up and look at it, what we have is not the thing inside of of it in our hands, but it’s just the wrapped box itself.

If we want to get at the thing inside, we need to unwrap the gift first. This is the same way optionals work. When we pass around optional variables and interact with them, we’re really working with a container that may or may not have anything inside of it. Similar to our gift, the optional must be unwrapped before it can be used.

Declaring our optional with no value is valid Swift and will compile just fine. But, if we tried to declare this variable without the optional syntax, it would present an error.

There are also variables in Swift that are not optional. They always have a value. If you tried to assign nil to a non-optional variable, you will get a compiler error:

var favoriteColor = "Blue"
favoriteColor = nil
error: nil cannot be assigned to type 'String'

Similarly, non-optional values can not be assigned to nil during their declaration:

var favoriteColor: String
error: variables must have an initial value

Unwrapping Optionals

So we know what optionals are, and that they allow for a variable to become nil, and we know that optionals are containers rather than values themeselves. So, in our programs when we want to access the contents of an optional, how do we do it? There are several ways so let’s go over them now.

First, and most commonly, you will unwrap optionals using optional binding. In optional binding, you will assign a new variable to the value of an optional within an if statement. If the optional contains a value, this variable will be set, and the code block following this statement will be executed.

Let’s look at an example. Here we will declare two optionals, one called favoriteAnimal which is set to Fox, and one set to favoriteSong, which we will not set (it will remain nil)

var favoriteAnimal: String?
var favoriteSong: String?
 
favoriteAnimal = "Fox"

Let’s employ optional binding to discover if each variable is set, and if so we’ll print a sentence containing the value to the console. First we’ll do it with favoriteAnimal.

if let unwrappedFavoriteAnimal = favoriteAnimal {
    print("Favorite animal is: " + unwrappedFavoriteAnimal)
}
Favorite animal is: Fox

In the event that the value is not set, we simply will trigger whatever is in the else block, or nothing at all if an else block isn’t specified.

if let unwrappedFavoriteSong = favoriteSong {
    print("Favorite song is: " + unwrappedFavoriteSong)
}
else {
    print("I don't know what your favorite song is!")
}
I don't know what your favorite song is!

If we need to unwrap multiple optionals, and we require all of them to proceed with a bit of logic, we need to check each one:

var favoriteAnimal: String?
var favoriteSong: String?
 
favoriteAnimal = "Fox"
favoriteSong = "Shake it Off"
 
if let unwrappedFavoriteSong = favoriteSong {
    if let unwrappedFavoriteAnimal = favoriteAnimal {
        print(unwrappedFavoriteSong + " " + unwrappedFavoriteAnimal)
    }
}

This gets kind of messy kind of fast, so Swift offers a shortcut to unwrap multiple variables at once:

var favoriteAnimal: String?
var favoriteSong: String?
 
favoriteAnimal = "Fox"
favoriteSong = "Shake it Off"
 
if let unwrappedFavoriteSong = favoriteSong,
    let unwrappedFavoriteAnimal = favoriteAnimal {
    print(unwrappedFavoriteSong + " " + unwrappedFavoriteAnimal)
}

Collections

Swift has multiple collection types, the most common of which being arrays, sets, and dictionaries.

Array

Let’s take a look at an example of an Array

let starks: [String] = ["Eddard", "Catelyn", "Robb", "Sansa"]

Here we have a basic Array, which is of type [String]. The square brackets indicate that this is an array of String objects, rather than just being a single String. As usual, Swift can infer this type data too, just by examining the initial assignment:

let starks = ["Robb", "Sansa", "Arya", "Jon"]

We can access elements of this array in a variety of ways, such as using an Int index, calling the various collections methods.

let starks = ["Robb", "Sansa", "Arya", "Jon"]
 
print( starks[0] )
print( starks[2] )
print( starks.first! )
Robb
Arya
Robb

You’ll note that arrays are zero-indexed, so the first element in the array "Robb" is accessed using starks[0]

Additionally, you may notice that while the first method returns an optional (and therefore is being force-unwrapped with the ! symbol), the indexed accessors does not return an optional. If you try to access an index in an array that is not present, your program will fail at runtime! So always check the length of arrays when accessing by index:

if starks.count >= 4 {
    print( starks[3] )
}

There are ways to make this type of checking automated, but it is not done by default for performance reasons.

Hashable Types / Dictionary

Dictionaries are able to store values based on a key, typically the key is of type String, but it could actually be many different Swift types. In this example we create a basic Dictionary with String keys and Int values for the age of each person:

let ages = ["Robb": 15, "Sansa": 12, "Arya": 10, "Jon": 15]

We can access these values by their String keys:

print( ages["Arya"]! )
print( ages["Jon"]! )
10
15

Note that we’re unwrapping these because they are optional values, and could potentailly be nil. It is generally safer to use optional binding to unwrap the value from a Dictionary, especially if you have reason to believe the value could often be nil.

if let aryasAge = ages["Arya"] {
    print("Arya is \(aryasAge) years old")
}
Arya is 10 years old

We can also store arrays inside of dictionaries, or dictionaries inside of arrays, or a mix of both.

let families = [
    "Stark": ["Robb": 15, "Sansa": 12, "Arya": 10, "Jon": 15],
    "Baratheon": ["Joffrey": 13, "Tommen": 8]
]
let tommensAge = families["Baratheon"]!["Tommen"]!
print("Tommen is \(tommensAge) years old")
Tommen is 8 years old

The type of houses here would be [String: [String: Int]]
Or in other words it is a dictionary with a String key, and it’s values are [String: Int], another dictionary with String keys and Int values.

Set

A Swift 3 Set is similar to an Array, except the values in a Set are unique and unordered.

Initializing a Set looks almost exactly like intitializing an Array, the only different is the type:

let colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]

This code creates a new set of type String. The greater than / less than symbols < and > are used to indicate Swift generic types, including the types of a Set as shown here.

You’ll notice we included "Blue" twice in our list, but if we print out the contents of colors, we only see it once:

let colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]
print(colors)
["Orange", "Red", "Blue"]

You may also notice that the ordering is inconsistent. Sets do not maintain any particular order for their contents.

We can not access members of a Set using indexes as we can with arrays, but instead we use the methods built-in to the Set type to add and remove objects. We can also call the contains method to check if the Set includes something.

var colors: Set<String> = ["Blue", "Red", "Orange", "Blue"]
colors.insert("Black")
colors.insert("Black")
colors.remove("Red")
print(colors)
print(colors.contains("Black"))
print(colors.contains("Red"))
["Black", "Orange", "Blue"]
true
false

Constructing sets of objects is a common way to catalogue what is included or excluded in a list of things, as long as there is no need to order or have duplicates of the objects.

There are many methods I have not mentioned, and I would encourage you to read through Apple’s documentation on each of these three classes to further familiarize yourself with them.

Tuples

Tuples are not technicaly a collection, but instead simply multiple variables that can be passed around with a single identifier.

let fullName = ("Jameson", "Quave")

The type of the tuple here is (String, String), and we can manually access each numbered tuple element using dot-syntax, followed by the index:

let fullName = ("Jameson", "Quave")
print(fullName.1)
print(fullName.0)
Quave
Jameson

Tuples can also be deconstructed in to new variable names:

let (first, last) = ("Jameson", "Quave")
print(first)
Jameson

Since we’re not using the last name here, we could just ignore that value by using an underscore _ and still deconstruct the first name:

let (first, _) = ("Jameson", "Quave")
print(first)
Jameson

Tuples are useful when you have a method that you want to return multiple values.

Control Flow

Control flow in Swift looks pretty similar to other languages. At the most basic level we have the if and else statements.

if 10 > 5 {
  print("10 is greater than 5.")
}
else {
    print("10 is not greater than five.")
}
10 is greater than 5

You can alternatively put your conditions for an if statement in parenthesis:

if (10 > 5) {
...

Swift also support the switch statement, and checks at compile for whether or not you have exhaustively covered all possibilities. If you do not (or can not) specifically handle all possibiities, then you can use the default: case to handle everything not explicitly handled.

let name = "Jameson"
switch(name) {
case "Joe":
  print("Name is Joe")
case "Jameson":
  print("This is Jameson")
default:
  print("I don't know of this person!")
}
This is Jameson

Here because the value of name is "This is Jameson", we match the 2nd case and execute the line

...
  print("This is Jameson")
...

If however we set the name to be something not present in our list of cases, such as "Jason", the switch would fall back to the default case.

let name = "Jason"
switch(name) {
case "Joe":
  print("Name is Joe")
case "Jameson":
  print("This is Jameson")
default:
  print("I don't know of this person!")
}
I don't know of this person!

Loops and Collections

Swift 3 does not support the classical C-style for loops you may be used to, and instead opts for enumeration and for-each style loops for the for element in array syntax.

For example if we have an array names, and we want to print each one seperately, we can do so with a for loop:

let names = ["Robb", "Sansa", "Arya", "Jon"]
 
for name in names {
    print("Name: \(name)")
}
Name: Robb
Name: Sansa
Name: Arya
Name: Jon

This is great if you happen to want to loop over an array, but without C-style arrays, how would we loop over a series of numbers? The answer comes in the form of Swift’s Range and Stride. Let’s say we wanted to count to 10, by threes, we could do that by using a Range from 1 to 10 using the syntax 1...10. Then we could only print each number that is evenly divisible by three by using the modulo % operator and checking for a remainder of 0.

for i in 1...10 {
    if i % 3 == 0 {
        print(i)
    }
}
3
6
9

There is another option however, to only iterate every third item (or any arbitrary delta), known as a stride. A stride can be created using a variety of methods, but the most common is stride(from: , to:, by:) where the from value is where the stride starts, to is where it ends, and by is how much each value changes to approach the to. If that sounds a little confusing just look at this code sample

let byThrees = stride(from: 3, to: 10, by: 3)
for n in byThrees {
    print(n)
}
3
6
9

It’s almost readable in english, you might say you are “counting” from 3 to 10 by 3. Here we create a stride and store it in a variable named byThrees, but we could use it directly in the loop as well:

for n in stride(from: 3, to: 10, by: 3) {
    print(n)
}
3
6
9

Collections also all have an indices property that can be used to loops. This returns an array of the indices a collection has, useful for iterating or filtering some but not all of a collection. For example, back in our name collection we may want only the first three names, which we can retrieve like so:

let names = ["Robb", "Sansa", "Arya", "Jon"]
for nameIndex in names.indices {
    if(nameIndex < 3) {
        print(names[nameIndex])
    }
}
Robb, Sansa, Arya

There is also the enumerate method on collections, that allow you to get both the index and value from an array as you loop over it:

let names = ["Robb", "Sansa", "Arya", "Jon"]
for (index, name) in names.enumerated() {
    print("\(index): \(name)")
}
0: Robb
1: Sansa
2: Arya
3: Jon

There are still more ways to enumerate loop over objects in Swift 3, but these are the most commonly used.

You may notice in our for loop we are assigning to two variables at once, both index and name. These are seperated by commas and surrounded by parenthesis in orer to delineate there are two named variable we expect returned from the enumerated() method. These are techincally deconstructed tuples.

This concluded the fundamentals portion of the Swift 3 tutorial. Next, I’ll show you how to use what you’ve learned here in real-world scenarios. Keep up with these posts by subscribing below. You’ll be emailed when I make new posts, and post video tutorials and other neat stuff, never spam 🙂

Follow me on Twitter


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS

Comment