SwiftUI ForEach: More Than Just Loops in Swift

SwiftUI ForEach allows you to show collections of data in your iOS and macOS apps. ForEach works with different types like arrays and ranges to sets, dictionaries, and even enumerated sequences. With its requirement for RandomAccessCollection, SwiftUI ensures efficient rendering of your user interface elements.

Moreover, ForEach can be used to construct more complex views within SwiftUI’s robust framework. Whether it’s populating List and ScrollView with dynamic content, filling up Picker with user-selectable options, or arranging views within layout constructs such as VStack or HStack, ForEach makes it easy.

This post will delve into the nuances of ForEach, how it stands apart from standard Swift loops, and why it’s much more than just a looping construct. If you’re looking for a refresher on traditional loops in Swift, you might want to check out this post on “How to use For Loops and For Each in Swift”.

In the following sections, I’ll provide practical examples that illustrate the workings of SwiftUI’s ForEach.

Basic Usage of ForEach

In SwiftUI, ForEach serves as an indispensable structure for handling dynamic data. It’s frequently used in situations where multiple views are created based on a collection of data items.

Perhaps the most straightforward usage of ForEach is in conjunction with List, enabling the dynamic creation of rows. For instance, if you have an array of food items

struct Food: Identifiable {
    var name: String
    var icon: String
    var isFavorite: Bool
    let id = UUID()
}

and want to create a view for each element, ForEach makes this operation straightforward:

struct ContentView: View {
    @State private var foods = [Food(name: "Apple", icon: "🍎", isFavorite: true),
                              ... ]

    var body: some View {
        List {
            ForEach(foods) { food in
                FoodRow(food: food)
            }

            Button(action: {
                let new = Food(name: "New",
                               icon: String(foods.count),
                               isFavorite: false)
                    foods.append(new)
            }, label: {
                Label("Add", systemImage: "plus")
            })
        }
    }
}
swiftui example where a foreach is used inside a list

In the above example, ForEach traverses the foods array, and for each food item, it generates a FoodRow view.

For what SwiftUI views do I need to use ForEach?

The SwiftUI `ForEach` view is primarily used in views that need to create multiple child views from a collection of data. The versatility of ForEach extends beyond generating simple lists – it is capable of creating a wide range of view sets, including:

  1. List: A `List` view can contain multiple rows of data. You can use `ForEach` inside a `List` to create a row for each item in your data collection. This is especially useful when dealing with dynamic data that can change over time.
  2. ScrollView: A `ScrollView` can be used to create a scrollable view of multiple elements. You can use `ForEach` inside a `ScrollView` to generate those elements based on your data. See some complex examples.
  3. LazyVStack / LazyHStack: When you want to arrange multiple views in a vertical or horizontal line, you can use `ForEach` with `LazyVStack` or `LazyHStack`. These views only load their contents as they come on screen, which makes them more efficient for large collections. If your data is small, you can use VStack, HStack, or ZStack.
  4. Picker: A `Picker` view is used to present a list of options that the user can choose from. You can use `ForEach` to generate these options dynamically from a data collection. Read further about Picker view.
  5. Grids (LazyVGrid / LazyHGrid): These views allow for the creation of grid-based layouts. `ForEach` can be used to generate each cell of the grid based on your data.
examples where foreach is used in SwiftUI like LazyVGrid, stacks, scrollviews and tableview
Examples where ForEach is used in SwiftUI like LazyVGrid, stacks, scroll views and table view.

Remember that ForEach isn’t a view itself, but rather a way to create views based on collections of data. The views that get created within ForEach can be of any type — Text, Image, etc., or even custom views that you define yourself.

What data can I use with ForEach Structure?

When working with SwiftUI’s ForEach structure, understanding the type of data it accepts is crucial. Essentially, ForEach requires the data to conform to the RandomAccessCollection protocol, allowing the efficient traversal of elements within the collection. Additionally, if the data collection comprises custom data types, these types should conform to the Identifiable protocol.

RandomAccessCollection

Swift’s RandomAccessCollection protocol extends the BidirectionalCollection protocol and represents a type of collection that supports efficient traversal and mutation of its elements at any position. Not all collection types in Swift conform to this protocol — it is mainly used by collections that can guarantee efficient random access to their elements. Arrays, for instance, conform to RandomAccessCollection, enabling you to access any element in constant time, irrespective of its position.

Why is this protocol crucial to ForEach? When SwiftUI renders views using ForEach, it should be able to access any item in the collection swiftly and directly. This makes RandomAccessCollection a vital requirement for the data used in ForEach inside List or Scroll views, where you want smooth and fast scrolling behavior.

Free SwiftUI Layout Book

Get Hands-on with SwiftUI Layout.

Master SwiftUI layouts with real-world examples and step-by-step guide. Building Complex and Responsive Interfaces.

Using Ranges with Foreach

In SwiftUI, ranges prove to be very handy when working with the ForEach structure. Ranges in Swift are a RandomAccessCollection, making them suitable for use with ForEach. They provide an efficient and succinct way to generate a certain number of views without explicitly defining an array.

Consider a scenario where you want to create an HStack that contains a series of color views. Each color view should have a distinct shade of red, with the shade varying linearly from 0 to 1. You can achieve this by using a range with ForEach:

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Color gradient")
                .font(.title)

            HStack(spacing: 1) {
                ForEach(0..<10) { number in
                    Color(red: Double(number) / 10,
                          green: 0.3,
                          blue: 0.75)
                }
            }
            .frame(height: 150)
        }
    }
}
using foreach with range to create a color gradient in swiftui

In above example, ForEach is generating ten Color views, using the range 0..<10, which uses a closed range operator. For each number in the range, it creates a Color view with a red intensity proportional to the number. As the number increases, the shade of red in the color view becomes more intense, ranging from no red (for 0) to full red (for 9).

This HStack is then given a frame height of 150 points. Without specifying a width, the HStack will horizontally fill the available space, and the Color views will be distributed accordingly. The end result is a gradient-like series of color views within the HStack, thanks to the combined power of ranges and the ForEach structure.

How SwiftUI uses the Identifiable Protocol to create uniquely identified data

Apart from RandomAccessCollection, another important protocol when dealing with ForEach is Identifiable. This protocol is used when you have a collection of custom data types. It ensures that each object in the collection is unique, which is essential for SwiftUI to understand which views to update when data changes.

The Identifiable protocol requires a unique identifier (id) for each instance. If your data type is a struct, it’s common to use a UUID for this ID. If it’s an Int or String type, you can use the instance’s value itself.

struct CustomData: Identifiable {
    let id = UUID()
    var name: String
}

In the above example, CustomData conforms to Identifiable by providing a unique UUID for each instance.

The following example shows how you then can use your custom data with foreach:

struct ContentView: View {
    let data = [CustomData(name: "one"),
                CustomData(name: "two"),
                CustomData(name: "three")]
    var body: some View {
        VStack {
          ForEach(data) { element in
             Text(element.name)
          }
        }
    }
}

Working with data that does not conform to Identifiable

If you work with data that is not Identifiable, e.g. an array of strings, you can use an initializer that has an id parameter. The id parameter is a key path to a property that uniquely identifies each element in the collection. In most cases, this is an id property that conforms to the Identifiable protocol, as described in the previous section. By providing the key path . (dot) followed by the unique identifier (usually id), you’re telling SwiftUI how to uniquely identify each item in the collection.

This form of ForEach creates views based on the data and the unique identifier you provide. When the data changes, SwiftUI uses these identifiers to determine which views to keep and which ones to recreate. This makes updates more efficient, especially for large collections, because SwiftUI can simply update the views corresponding to the changed data, instead of recreating all views.

The following example uses an array of Strings:

struct ContentView: View {
    let data = ["one", "two", "three"]
    var body: some View {
        VStack {
          ForEach(data, id: \.self) { element in
             Text(element.name)
          }
        }
    }
}

In this example, ForEach will generate a new Text view for each element in the data array, using the element itself ‘self’to uniquely identify each view.

Here’s an example with a list of custom Task items:

struct Task {
    var id = UUID()
    var name: String
}

let tasks = [Task(name: "First"), Task(name: "Second"), Task(name: "Third")]

ForEach(tasks, id: \.id) { task in
    Text(task.name)
}

ForEach will generate a new Text view for each Task in the tasks array, using the Task‘s id to uniquely identify each view.

Why Identified data is important to work with animation in SwiftUI ForEach?

Animations are integral to providing a smooth, responsive user interface in SwiftUI. However, to ensure the seamless operation of these animations, particularly within a ForEach structure, identifiable data is paramount.

When SwiftUI’s ForEach operates on data that conforms to the Identifiable protocol, it creates and maintains a strong link between each piece of data and the corresponding view. This connection plays a vital role when data changes result in animated transitions.

Consider a ForEach loop generating a list of views. If an item is added, removed, or moved within the data collection, SwiftUI will need to perform the corresponding animation on the associated view. Without identifiable data, SwiftUI would not be able to accurately track which view corresponds to which piece of data, making these animations disordered or inaccurate.

For instance, if an item is removed from the collection, SwiftUI uses the id from the Identifiable protocol to correctly identify and remove the associated view. Similarly, if an item is added, SwiftUI will create a new view for that item and animate it into the correct position.

struct Task: Identifiable {
    var id = UUID()
    var name: String
}

struct ContentView: View {
    @State var tasks = [Task(name: "First"), Task(name: "Second"), Task(name: "Third")]

    var body: some View {
        List {
            ForEach(tasks) { task in
                Text(task.name)
                    .transition(.slide)
            }.onDelete{}
        }
    }
}

In above example, if you add or remove tasks, SwiftUI uses the id property to animate the changes correctly. As each Task has a unique id, SwiftUI can track each Task view across insertions, deletions, and moves, resulting in accurate animations.

When working with animations within SwiftUI’s ForEach, identifiable data is essential. It ensures that animations correctly correspond to their associated views, providing a fluid and responsive user interface.

Foreach with Sets and Dictionaries

Swift’s ForEach view is designed to accommodate a wide range of collection types, including sets and dictionaries. While these collections don’t conform to the RandomAccessCollection protocol and don’t provide a guaranteed order, you can still use them effectively with ForEach by transforming them into sorted arrays.

Sets with ForEach

A Set in Swift is an unordered collection of unique elements. Although it doesn’t maintain a particular order, we can still use a Set with ForEach by converting it into a sorted array:

struct ContentView: View {
    let uniqueNumbers: Set = [1, 2, 3, 4, 5]

    var randomOrderedArray: [Int] {
        Array(uniqueNumbers)
    }

    var sortedArray: [Int] {
        uniqueNumbers.sorted()
    }

    var body: some View {
        VStack {
            Text("Random Numbers")
                .font(.title2).padding()

            ForEach(randomOrderedArray, id: \.self) { number in
                Text("Number \(number)")
            }

            Divider()

            Text("Ordered Numbers")
                .font(.title2).padding()

            ForEach(sortedArray, id: \.self) { number in
                Text("Number \(number)")
            }
        }
    }
}
implemenation of swift set with swiftui foreach

In this example, ForEach operates on two different arrays derived from the uniqueNumbers set. The randomOrderedArray retains the original unordered nature of the set, whereas the sortedArray is sorted in ascending order using Swift’s built-in sorted() function. The result is two distinct lists of numbers, with the second one always presented in increasing order.

Free SwiftUI Layout Book

Get Hands-on with SwiftUI Layout.

Master SwiftUI layouts with real-world examples and step-by-step guide. Building Complex and Responsive Interfaces.

Dictionaries with ForEach

Dictionaries in Swift are collections of unordered key-value pairs. To use a dictionary with ForEach, you can transform it into an array of keys, values, or key-value pairs. To maintain an order, sort this array accordingly:

let numberNames: [Int: String] = [1: "One", 2: "Two", 3: "Three"]

ForEach(numberNames.keys.sorted(), id: \.self) { key in
    Text("Number \(key) is called \(numberNames[key]!)")
}

In this case, ForEach operates on the sorted array of keys from the numberNames dictionary. The Text views display the number and its corresponding name, with the numbers always presented in ascending order.

Foreach with Enum type data and Picker views

Swift’s enumeration, or enum, is a common data type used in various scenarios. With ForEach, you can efficiently generate dynamic SwiftUI views based on enum cases. This approach is particularly effective when populating SwiftUI’s Picker views with options derived from an enum.

Let’s consider a simple enum type WeatherType, which represents different types of weather:

enum WeatherType: String, CaseIterable, Identifiable {
    case sunny = "Sunny"
    case cloudy = "Cloudy"
    case rainy = "Rainy"
    case snowy = "Snowy"
    var id: String { self.rawValue }
}

In this enum, CaseIterable provides a collection of all the enum’s cases, and Identifiable allows SwiftUI to distinguish between each case.

Now, suppose you want to create a Picker view that lets the user select the weather type. You can use ForEach to generate the picker’s options based on WeatherType:

enum WeatherType: String, CaseIterable, Identifiable {
    case sunny = "Sunny"
    case cloudy = "Cloudy"
    case rainy = "Rainy"
    case snowy = "Snowy"
    var id: String { self.rawValue }
}
struct ContentView: View {
    @State private var selectedWeather = WeatherType.sunny

    var body: some View {
        VStack {
            Text("Select a weather type")
                .font(.title2).padding()

            Picker("Weather Type",
                   selection: $selectedWeather) {
                ForEach(WeatherType.allCases) { weatherType in
                    Text(weatherType.rawValue)
                        .tag(weatherType)
                }
            }
            .pickerStyle(MenuPickerStyle())
        }
    }
}
how to use foreach with picker view in swiftui

Here, ForEach(WeatherType.allCases) creates a Text view for each case in WeatherType. The .tag(weatherType)associates each option with its corresponding enum case, allowing the picker to update selectedWeather accurately when a different option is selected.

In this manner, ForEach and enums come together to create dynamic and interactive SwiftUI views. They allow for the efficient rendering of options in a Picker view, ultimately leading to more streamlined and readable code.

Getting the Index of your Data inside Foreach with Enumerated

Sometimes, while iterating over a collection using ForEach, you may want to access the current index of each item. In Swift, the enumerated() method is commonly used to return a sequence of pairs (a tuple), consisting of an index and the associated element from the collection. However, because enumerated() returns a sequence which doesn’t conform to RandomAccessCollection, it can’t be used directly with SwiftUI’s ForEach.

To work around this, you can use the Array initialiser to convert the sequence into an array before using it with ForEach. This approach works well because arrays conform to RandomAccessCollection.

Here’s how you can get the index of your data inside ForEach:

struct ContentView: View {
    let numbers = [1, 2, 3, 4, 5]

    var body: some View {
        VStack {
            Text("Indexed Numbers")
                .font(.title2).padding()

            List {
                ForEach(Array(numbers.enumerated()), id: \.element) { index, number in
                    Text("Number \(number) is at index \(index)")
                }
            }
        }
    }
}

In this example, numbers.enumerated() creates a sequence of index-number pairs, and Array(numbers.enumerated())converts that sequence into an array. Then, ForEach iterates over this array. For each item, it creates a Text view that displays both the number and its index in the original numbers array. The .element key path is used to provide a unique identifier for each element.

By using enumerated() and ForEach together in this way, you can create dynamic views based on both the data in a collection and each item’s position within that collection.

Edit Your Data inside a ForEach

If you want to edit an element inside ForEeach, you can use now an ForEach initializer with a binding. Continuing with the previous example, I want to add a ´isFavorite´ button to my custom Data:

struct CustomData: Identifiable {
    let id = UUID()
    var name: String
    var isFavorite: Bool = false
}

You can then use the binding value inside the ForEach:

struct ContentView: View {
    let data = [CustomData(name: "one"),
                CustomData(name: "two"),
                CustomData(name: "three")]
    var body: some View {
        List {
          ForEach($data) { $element in
            HStack {
              Text(element.name)
              Image(systemName: element.isFavorite ? "heart.fill" : "heart")
                .onTapGesture {
                    $element.isFavorite.toggle()
                }
            }
          }
        }
    }
}

Note that you can also use this directly with List:

struct ContentView: View {
    let data = [CustomData(name: "one"),
                CustomData(name: "two"),
                CustomData(name: "three")]
    var body: some View {
        List($data) { $element in
            HStack {
              Text(element.name)
              Image(systemName: element.isFavorite ? "heart.fill" : "heart")
                .onTapGesture {
                    $element.isFavorite.toggle()
                }
            }

        }
    }
}

Conclusion

SwiftUI ForEach allows you to show collections of data in your iOS and macOS apps. ForEach works with different types like arrays and ranges to sets, dictionaries, and even enumerated sequences. With its requirement for RandomAccessCollection, SwiftUI ensures efficient rendering of your user interface elements.

Moreover, ForEach can be used to construct more complex views within SwiftUI’s robust framework. Whether it’s populating List and ScrollView with dynamic content, filling up Picker with user-selectable options, or arranging views within layout constructs such as VStack or HStack, ForEach makes it seamless.

Ultimately, SwiftUI’s ForEach structure is a testament to the expressive and efficient coding paradigm that SwiftUI offers, providing developers with a high level of abstraction while maintaining a simple and readable codebase. The possibilities are extensive, and the power to create is in your hands. Happy coding!

3 thoughts on “SwiftUI ForEach: More Than Just Loops in Swift”

  1. Hi ѡould you mind letting me know which web host you’re working with?
    I’ve loaded your blog in 3 different browsers and I must say this blog loads а lot faster then most.
    Can you recοmmend a good internet hosting
    рrovider at a honest price? Thanks, I appreciate it!

    Reply
  2. Hi Karin. I tried the exaple when use the binding value inside the ForEach an got the compiler error “Cannot find ‘$data’ in scope” in line 7.

    Reply

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!

Newsletter Form