Complete Guide to SwiftUI ForEach

swiftui Oct 08, 2024
SwiftUI ForEach

Instead of manually creating SwiftUI views for each element in a collection, ForEach is commonly used to generate them dynamically. ForEach is a struct that creates a view for every element in a data collection. It’s typically used within containers like List, ScrollView, Picker, and Grid to loop through the data and render views for each element.

Table of Contents

  1. How to create views in a loop using ForEach
  2. How to use Range with ForEach
  3. SwiftUI ForEach without Identifiable data
  4. SwiftUI ForEach with array
  5. SwiftUI ForEach with Identifiable data
  6. How to use ForEach in SwiftUI List
  7. How to get index in SwiftUI ForEach
  8. How to iterate through dictionary with ForEach
  9. How to iterate through enum cases with ForEach
  10. How to create ForEach from a binding

 

How to create views in a loop using ForEach

The initialiser for ForEach takes a collection as one of the parameters which must conform to the RandomAccessCollection protocol.

Additionally, SwiftUI needs a way to uniquely identify each element in the collection so that it can update the view and manage the state when the data in the collection changes. This can be achieved in two ways, which we'll discuss in detail in the SwiftUI ForEach without Identifiable data and SwiftUI ForEach with Identifiable data sections.

struct ContentView: View {
    var body: some View {
        ForEach(data) {
            // content goes here
        }
    }
}

 

What is RandomAccessCollection protocol

As the name suggests, this protocol allows random access to the collection elements using integer indices in constant time i.e. O(1). The reason why ForEach relies on this protocol is simply to ensure quick and direct access to elements, which is essential for rendering views smoothly and handling updates.

In SwiftUI, the most commonly known collections which conform to the RandomAccessCollection protocol are Array and Range.

 

How to use Range with ForEach

ForEach can iterate over a range of numbers, as ranges conform to the RandomAccessCollection protocol. However, SwiftUI handles half-open and closed ranges differently.

Using a half-open range:

In case of half-open range Range (a..<b, b is not included), SwiftUI infers identity of the elements automatically, so you don’t need to specify a unique identifier.

struct ContentView: View {
    var body: some View {
        ZStack {
            ForEach(0..<30) { index in
                Rectangle()
                    .stroke(Color.blue, lineWidth: 2)
                    .frame(width: 250 - CGFloat((index * 10)), height: 250 - CGFloat((index * 10)))
            }
        }
    }
}

Result:

The above code creates 30 rectangles, each slightly smaller than the previous one and the size is determined by the range element received in each iteration.

Using a closed range:

In case of closed range ClosedRange (a...b, a & b both are included), SwiftUI doesn't infer the identity by default, so you need to provide an explicit identifier. If you don't do so, you'll get an error.

struct ContentView: View {
    var body: some View {
        ZStack {
            ForEach(0...30) { index in
                Rectangle()
                    ...
            }
        }
    }
}

Error:

Let's see how to resolve this error in the next section.

 

SwiftUI ForEach without Identifiable data

In cases where SwiftUI is unable to uniquely identify each element in the collection, one of the ways to resolve this is to pass the idparameter to the ForEach initialiser. The id is of type KeyPath which is used to provide reference to the property that uniquely identifies each element.

You can either create a unique property for each element in the collection and use it as id or a simpler and most preferred way is to use the identity key path (.self) which refer to the whole instance instead of a property.

struct ContentView: View {
    var body: some View {
        ZStack {
            ForEach(0...30, id: \.self) { index in
                Rectangle()
                    ...
            }
        }
    }
}

Result:

 

SwiftUI ForEach with array

For iterating over an array in SwiftUI, you can simply pass it to the ForEach initialiser since array confirms to the RamndomAccessCollection protocol. You also need to use the id parameter for allowing SwiftUI to uniquely identify the elements.

Let's see how to iterate over an array in SwiftUI using ForEach.

struct ContentView: View {
    let employees = ["Jake", "Jared", "Michael"]
    
    var body: some View {
        ForEach(employees, id: \.self) { employee in
            Text(employee)
        }
    }
}

Result:

Let's take another example where instead of using the identity key path, we'll create an array of tuples where each tuple has unique property and then use it as id. The result however is going to be the same.

struct ContentView: View {
    let employees = [
        (employeeID: 427, name: "Jake"),
        (employeeID: 189, name: "Jared"),
        (employeeID: 356, name: "Michael")
    ]
    
    var body: some View {
        ForEach(employees, id: \.employeeID) { employee in
            Text(employee.name)
        }
    }
}

 

SwiftUI ForEach with Identifiable data

The second approach for unique identification is to conform the collection elements to the Identifiable protocol. In this case, you don't have to explicitly pass the id parameter to ForEach, rather SwiftUI automatically use the id property defined in the data model to identify each element. This approach is suitable when you're working with custom and complex data types.

Let's create a custom data model conforming to the Identifiable protocol. This protocol requires the type to have an id property that uniquely identifies each instance.

struct Employee: Identifiable {
    let id = UUID()
    let name: String
    let role: String
}

Now, let's create an array of employees and use it directly inside ForEach.

struct ContentView: View {
    let employees = [
        Employee(name: "Jake", role: "Software Engineer"),
        Employee(name: "Jared", role: "Product Manager"),
        Employee(name: "Michael", role: "UX Designer")
    ]
    
    var body: some View {
        ForEach(employees) { employee in
            HStack() {
                Text(employee.name)
                    .font(.headline)
                Text(employee.role)
                    .font(.subheadline)
                    .foregroundColor(.gray)
            }
        }
    }
}

Result:

 

How to use ForEach in SwiftUI List

When you want to display a collection of data in a List, you can use ForEach to loop over the elements. ForEach gives the flexibility to present multiple collections as well as static and dynamic content together within the List.

var body: some View {
    List {
        ForEach(employees) { employee in
            HStack() {
                Text(employee.name)
                    .font(.headline)
                Spacer()
                Text(employee.role)
                    .font(.subheadline)
                    .foregroundColor(.gray)
            }
        }
    }
}

Result:

 

How to get index in SwiftUI ForEach

Sometimes, you might also need to access the index of the elements while iterating through a collection with ForEach. SwiftUI doesn't provide this functionality directly, but you can achieve this using the enumerated() method. This method returns a sequence of pair of the index and the corresponding element.

var body: some View {
    ForEach(Array(employees.enumerated()), id: \.offset) { index, employee in
        Text("\(index + 1). \(employee.name) - \(employee.role)")
    }
}

In the above code,

  1. employee.enumerated() returns a sequence that doesn't conform to the RandomAccessCollection protocol and that's why it is wrapped in Array() to convert it to an array.
  2. The enumerated() method returns a sequence of tuple of the index and the corresponding element. These are called as offset and element respectively. To uniquely identify each element, we have used the offset i.e. index of each element.
  3. In the closure, we can access both the index and the employee.

Result:

 

How to iterate through dictionary with ForEach

Dictionaries are an unordered collection of key-value pairs which doesn't conform to the RandomAccessCollection protocol and that is why you can't use a dictionary directly with ForEach. To use a dictionary, you'll have to convert the dictionary keys into an array and pass it to the ForEach initialiser.

Since dictionary is unordered, the representation of the elements will be random but in case you want to present it in a sorted manner, you can sort the array as well.

struct ContentView: View {
    let employees: [Int: String] = [
        427: "Jake",
        189: "Jared",
        356: "Michael"
    ]
    
    var body: some View {
        ForEach(Array(employees.keys).sorted(), id: \.self) { key in
            Text(employees[key]!)
        }
    }
}

Result:

 

How to iterate through enum cases with ForEach

Enums are often used with ForEach in SwiftUI in several scenarios where you need to represent a fixed set of options, for example as segmented controls, picker options etc.

Iterating through the enum cases with ForEach is quite straightforward. You simply need to ensure that the enum that you're creating is conforming to the CaseIterable protocol to get access to the allCases property that returns all cases of the enum and you can use any of the two approaches discussed earlier to uniquely identify the elements.

enum EmployeeRole: String, CaseIterable, Identifiable {
    case manager
    case developer
    case designer
        
    var id: Self { self }
}
    
struct ContentView: View {
    var body: some View {
        ForEach(EmployeeRole.allCases) { role in
                Text(role.rawValue.capitalized)
        }
    }
}

Result:

 

How to create a ForEach from a binding

When working with ForEach in SwiftUI, you might need to modify the data in the collection directly inside the ForEach loop. To do so, you will have to pass a Binding of the collection to ForEach initialiser. This not only display the data but also update it in real-time, with changes reflected immediately in the associated collection.

Let's say you want to give a toggle feature to capture whether the employee is a Senior employee or not.

struct Employee: Identifiable {
    let id = UUID()
    var name: String
    var isSenior: Bool
}

struct ContentView: View {
    @State private var employees = [
        Employee(name: "Jake", isSenior: false),
        Employee(name: "Jared", isSenior: true),
        Employee(name: "Michael", isSenior: false)
    ]
    
    var body: some View {
        List {
            ForEach($employees) { $employee in
                HStack {
                    Text(employee.name)
                        .font(.headline)
                    Spacer()
                    Toggle("Senior Employee", isOn: $employee.isSenior)
                }
                .labelsHidden()
            }
        }
    }
}

Result:

 

Where to go next?

In this article, you learned about how to iterate through multiple type of collections using ForEach such as range, dictionary and enums. You also lerant in detail about the two approaches to uniquely identify collection elements within ForEach and much more. Now, you can do some practice by exploring in which all scenarios you can make use of ForEach in your code. We would highly recommend you to read about more related articles like For loop in Swift and Complete Guide to List in SwiftUI.

Signup now to get notified about our
FREE iOS Workshops!