Rudrank Riyam Apple Platforms developer. WWDC '19 scholar.

for-in loops in Swift tutorial

7 min read 1996

For-in Loops in Swift Tutorial

In layman’s terms, when something runs in a loop, it repeats the same things again and again. For example, a loop would be iterating through the number of blog posts and displaying them on the main page.

There are different types of loops for control flow in Swift. These are for-in, forEach, while, and repeat-while loops. In this article, we’ll go through a basic overview of for-in loops in Swift. Then, we’ll demonstrate how to work with them using examples and use cases with different data types.

We’ll focus on the following:

  • The syntax of for-in loops
  • Arrays
  • Range and stride
  • Dictionaries
  • Enums

To follow along, you should have basic knowledge of the Swift language.

The syntax of for-in loops

The syntax starts with the word for, followed by the particular element in a loop that is created as a constant. We follow it by the word in and, finally, the sequence you want to loop over:

for element in elements {
    // do something with the element
}

For example, we have a list of stocks, each including its price, at a particular date:

struct Stock {
    var name: String
    var price: Double
    var date = Date()
}

We want to loop over the array and print the data for each stock. The syntax for the loop will look like this:

// MARK: - EXAMPLE
func printDetails(for stocks: [Stock]) {
    for stock in stocks {
        print(stock.name)
        print(stock.price)
        print(stock.date)
    }
}

// MARK: - USAGE
let stocks = [Stock(name: "Banana", price: 125),
              Stock(name: "TapeBook", price: 320),
              Stock(name: "Ramalon", price: 3200)]

printDetails(for: stocks)

// MARK: - OUTPUT
Banana
125.0
2021-05-21 22:40:42 +0000
TapeBook
320.0
2021-05-21 22:40:42 +0000
Ramalon
3200.0
2021-05-21 22:40:42 +0000

With knowledge of the basic syntax, let’s move on to looping the fundamental data structure: Array!

Arrays

From the official Swift documentation, “An array stores values of the same type in an ordered list. The same value can appear in an array multiple times at different positions.”

We use for-in loops to iterate over the stored values and then access each value in the array.

We made a custom demo for .
No really. Click here to check it out.

Basic example

Assume an app where we’re tracking a user jogging. At every location, we want to track their speed. Thus, in the app, we receive an array of locations:

let locations: [CLLocation] = []

We loop through the array, and for each location, we print the speed at that particular location:

for location in locations {
    print("The speed at location (\(location.coordinate.latitude), \(location.coordinate.longitude) is \(location.speed)")
}

Taking another illustration, we create a two-dimensional 10×10 array and print the value at each point:

var board: [[Int]] = Array(repeating: Array(repeating: 0, count: 10), count: 10)

for row in board {
    for number in row {
        // prints 0, hundred times
        print(number)
    }
}

Using the where clause

There are cases where we want to restrict the sequence only to elements that match a particular condition. In this scenario, we use the where keyword.

In a to-do app, we need the subset of completed goals out of all goals. Assume a model like this:

struct Goal: Identifiable, Hashable {
    var id = UUID()
    var name: String = "Goal Name"
    var date = Date()
    var goalCompleted: Bool = false
}

And our app has an array for Goal. We want to loop through the array and access only those goals that are completed:

// MARK: - EXAMPLE
func getCompletedGoals(for goals: [Goal]) {
    for goal in goals where goal.goalCompleted == true {
        /// Access to completed goals only.
        print(goal)
    }
}

// MARK: - USAGE
let goals = [Goal(name: "Learn basic syntax of for-in loops", goalCompleted: true),
             Goal(name: "Read about for-in loops and dictionaries"),
             Goal(name: "Read about for-in loops and enums")]

getCompletedGoals(for: goals)

// MARK: - OUTPUT
Goal(id: B7B148D6-853B-486A-8407-CD03A904B348, name: "Learn basic syntax of for-in loops", date: 2021-05-21 22:50:38 +0000, goalCompleted: true)

Using enumerated()

To access each index of the element simultaneously, we can use the instance method enumerated(). It returns a sequence of pairs that contain the index as well as the value of the element. Taking the previous example, if we want to list the index of the location in the array, we can write this:

for (index, location) in locations.enumerated() {
    print("The speed at location (\(location.coordinate.latitude), \(location.coordinate.longitude) is \(location.speed)")

    print("The index for this location is \(index)") 
}

Using indices

If we only want the index of the element in the array, we can use indices. This represents the valid indices in an array in ascending order. It loops from 0 to the last element in the array, i.e., array.count:

for index in array.indices {
    // Access the index
}

Using the two-dimensional array we created earlier, we iterate through each point and assign it a random integer value:

// MARK: - EXAMPLE
func updateValues(of board: inout [[Int]]) {
    for rowIndex in board.indices {
        for columnIndex in board[0].indices {
            board\[rowIndex\][columnIndex] = Int.random(in: 0..<10)
        }

        print(board[rowIndex])
    }
}

// MARK: - USAGE
var board: [[Int]] = Array(repeating: Array(repeating: 0, count: 10), count: 10)

updateValues(of: &board)

// MARK: - OUTPUT
[9, 4, 1, 7, 5, 2, 6, 4, 7, 4]
[1, 0, 1, 0, 5, 4, 5, 6, 7, 9]
[4, 7, 6, 3, 8, 9, 3, 5, 9, 5]
[8, 0, 9, 9, 6, 1, 2, 0, 2, 7]
[3, 7, 4, 1, 3, 4, 9, 9, 5, 6]
[5, 2, 5, 1, 8, 1, 8, 0, 0, 1]
[0, 4, 3, 4, 0, 6, 1, 8, 7, 5]
[7, 7, 7, 9, 1, 3, 6, 4, 0, 1]
[9, 5, 6, 5, 3, 8, 0, 1, 3, 4]
[1, 7, 7, 3, 1, 0, 7, 4, 5, 6]

Using an optional pattern

In a case where the sequence contains optional values, we can filter out the nil values using for case let, executing the loop for non-nil elements only.

From the previous example of the to-do app, let’s assume some of our goals have no value. The getCompletedGoals(for goals:) now accepts an array of the optional Goal:

// MARK: - EXAMPLE
func getCompletedGoals(for goals: [Goal?]) {
    for case let goal? in goals where goal.goalCompleted == false {
        /// Access to completed goals only.
        print(goal)
    }
}

// MARK: - USAGE
let goals: [Goal?] = [Goal(name: "Learn something new!", goalCompleted: true),
                      Goal(name: "Read about for-in loops and dictionaries"),
                      nil,
                      Goal(name: "Read about for-in loops and enums"),
                      nil]

getCompletedGoals(for: goals)

// MARK: - OUTPUT
Goal(id: F6CB6D77-9047-4155-99F9-24F6D178AC2B, name: "Read about for-in loops and dictionaries", date: 2021-05-21 23:04:58 +0000, goalCompleted: false)
Goal(id: 822CB7C6-301C-47CE-AFEE-4B17A10EE5DC, name: "Read about for-in loops and enums", date: 2021-05-21 23:04:58 +0000, goalCompleted: false)

Range and stride

We can also use for-in loops for looping through hardcoded numeric ranges. They can be divided into two parts:

  • Using a closed range operator ()
  • Using a half-open range operator (..<)

Using a closed range operator

A closed range operator creates a range including both the end elements. A basic example of working with this operator is printing 10 numbers. Here, both 1 and 10 will be printed as well:

for number in 1...10 {
    print("The number is \(number)")
}

FizzBuzz is a simple programming exercise where we can use for for-in loops. The prompt is along these lines:

Write a program that prints numbers from 1 to n. Multiples of 3 print “Fizz” instead of the number and multiples of 5 print “Buzz.” For numbers that are multiples of both 3 and 5, print “FizzBuzz” instead of the number.

We loop through numbers 1 to n using the closed range operator to create a ClosedRange<Int> constant. Then, we again loop through the tuple in mapping and check for each element in the tuple. If the number is a multiple of 3, we append Fizz to the string.

As we check for each element in mapping, if it is also a multiple of 5, we append Buzz to the string with the result being FizzBuzz:

// MARK: - EXAMPLE
func fizzBuzz(for lastNumber: Int) {
    var result = [String]()
    let mapping = [(number: 3, value: "Fizz"), (number: 5, value: "Buzz")]

    for number in 1...lastNumber {
        var string = ""

        for tuple in mapping {
            if number % tuple.number == 0 {
                string += tuple.value
            }
        }

        if string == "" {
            string += "\(number)"
        }

        print(result)
    }
    return result
}

// MARK: - USAGE
fizzBuzz(for: 10)

// MARK: - OUTPUT
["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz"]

Using a half-open range operator

A half-open range operator creates a range excluding the last element. A basic example of working with this operator is accessing the indices of an array:

for index in 0..<array.count {
    // Access the index
}

Using stride

For cases where you want to skip elements in a loop by a particular number, you can use stride. We can also use this to go backward in a loop, starting from the last element and going to the first one.

Coming back to the example where we created a two-dimensional matrix of size 10×10 with random values, we want to print every alternate element in the first row:

// MARK: - EXAMPLE
func printFirstRow(for board: [[Int]]) {
    for rowIndex in stride(from: board.count - 1, through: 0, by: -2) {
        print(board\[rowIndex\][0])
    }
}

// MARK: - USAGE
printFirstRow(for: board)

// MARK: - OUTPUT
7
4
4
4
8

Now, we want to print every alternate element in the first column, but in the reverse order:

// MARK: - EXAMPLE
func printFirstColumn(for board: [[Int]]) {
    for rowIndex in stride(from: board.count - 1, through: 0, by: -2) {
        print(board\[rowIndex\][0])
    }
}

// MARK: - USAGE
printFirstColumn(for: board)

// MARK: - OUTPUT
8
6
0
6
5

Dictionaries

We can also iterate through a Dictionary using for-in loops, although the result will be unordered. The syntax is similar to arrays, with each element having its key and its value:

// MARK: - EXAMPLE
func printDictionary(for numbers: [Int: Int]) {
    for number in numbers {
        // number is a Dictionary<Int, Int>.Element
        print("The value for key \(number.key) is \(number.value)")
    }
}

// MARK: - USAGE
let numbers: [Int: Int] = [1: 2, 2: 3, 3: 4]

printDictionary(for: numbers)

// MARK: - OUTPUT
The value for key 1 is 2
The value for key 2 is 3
The value for key 3 is 4

We can also explicitly use our own keywords instead:

// MARK: - EXAMPLE
func printStockPrices(for stocks: [String: Int]) {
    for (name, price) in stocks {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: [String: Int] = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Banana is currently valued at $125.
Ramalon is currently valued at $3200.
TapeBook is currently valued at $320.

We can use where in dictionaries as well:

// MARK: - EXAMPLE
func printStockPrices(for stocks: [String: Int]) {
    for (name, price) in stocks where name == "Banana" {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: [String: Int] = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Banana is currently valued at $125.

If you want the highest price in this dictionary, you can sort the dictionary using sorted(by:):

// MARK: - EXAMPLE
func printStockPrices(for stocks: [String: Int]) {
    for (name, price) in stocks.sorted(by: { $0.value > $1.value }) {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: [String: Int] = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Ramalon is currently valued at $3200.
TapeBook is currently valued at $320.
Banana is currently valued at $125.

Using KeyValuePairs

As mentioned earlier, the Dictionary doesn’t have defined ordering. If you want ordered key-value pairs, you can use KeyValuePairs. This is useful in cases where you’re willing to sacrifice the fast, constant look-up time for linear time:

// MARK: - EXAMPLE
func printStockPrices(for stocks: KeyValuePairs<String, Int>) {
    for (name, price) in stocks {
        print("\(name) is currently valued at $\(price).")
    }
}

// MARK: - USAGE
let stocks: KeyValuePairs = ["Banana": 125, "TapeBook": 320, "Ramalon": 3200]

printStockPrices(for: stocks)

// MARK: - OUTPUT
Banana is currently valued at $125.
TapeBook is currently valued at $320.
Ramalon is currently valued at $3200.

Enums

You can even iterate over an enum in Swift by conforming to a specific protocol named CaseIterable. This type provides a collection of all its values. In our case, it gives all the cases in Enum. To access them, we use the allCases property.

With yet another example, we are working on a hyper-casual game. We need to set different game modes on the main screen. We’ll create an enum and iterate over it to access the mode name and the image name:

enum GameModes: String {
    case arcade
    case challenge
    case casual
    case timed
}

extension GameModes {
    var name: String {
        self.rawValue.capitalized
    }

    var image: String {
        switch self {
            case .arcade: return "🕹"
            case .casual: return "🎮"
            case .challenge: return "🎖"
            case .timed: return "⏳"
        }
    }
}

extension GameModes: CaseIterable {}

// Usage
for mode in GameModes.allCases {
    let gameOptionsView = GameOptionsView()
    gameOptionsStackView.addArrangedSubview(gameOptionsView)

    gameOptionsView.set(name: mode.name, image: mode.image)
}

Conclusion

Loops are fundamental knowledge that helps you become better at Swift. In this article, we covered an overview of for-in loops with different examples and use cases.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Rudrank Riyam Apple Platforms developer. WWDC '19 scholar.

Leave a Reply