ForEach
The official document: developer.apple.com/documentati…
public struct ForEach<Data.ID.Content> where Data : RandomAccessCollection.ID : Hashable {
public var data: Data
public var content: (Data.Element) - >Content
}
Copy the code
In the to-do list case, we use the ForEach view. ForEach generates views from a collection of objects and then combines them into a single View. Greatly simplified similar code, but also to solve the ViewBuilder parameters limit problem. It’s very common in SwiftUI development, so let’s dig deeper into ForEach today.
The code involved in ForEach use in the to-do list case is as follows:
/// Identifiable
struct TodoItem: Identifiable {
var id: UUID = UUID(a)var task: String
var imgName: String
}
@State var listData: [TodoItem] = [
TodoItem(task: "Write a SwiftUI article", imgName: "pencil.circle"),
TodoItem(task: "Watch a WWDC video.", imgName: "square.and.pencil"),
TodoItem(task: "Order out.", imgName: "folder"),
TodoItem(task: "Follow OldBirds public account", imgName: "link"),
TodoItem(task: "Run 2 kilometers at 6:30.", imgName: "moon"),]var body: some View {
NavigationView {
List {
Section(header: Text("To Do")) {
ForEach(listData) { item in
HStack{
Image(systemName: item.imgName)
Text(item.task)
}
}
.onDelete(perform: deleteItem)
.onMove(perform: moveItem)
}
Section(header: Text("Other content")) {
Text("Hello World")
}
}
.listStyle(GroupedListStyle())
.navigationTitle(Text("To Do list"))}}Copy the code
Let’s take a closer look at ForEach by asking two questions:
ForEach
What are the initialization methods?TodoItem
Why implementIdentifiable
The agreement?
ForEach
The initialization method of
ForEach is a view created from a collection of data recognized by an element. It supports three types of initialization:
- Iterating through range
- For by the implementation
Identifiable
Iteration of the set of elements composed of - The pair can be marked, but not implemented
Identifiable
Iteration of the set of elements composed of
range
This initializer is the simplest in ForEach. It is similar to a common for loop:
struct ContentView: View {
let data = ["Write a SwiftUI article"."Watch a WWDC video."."Order takeout."."Follow OldBirds public account"."Run 2 kilometers at 6:30.",]
var body: some View {
List {
ForEach(0..<data.count) { index in
Text(data[index])
}
}
}
}
Copy the code
Note, however, that this view is rendered only once. Therefore, if data.count changes, the view will not be updated. For example, clicking add to-do in the following code returns an error:
struct ContentView: View {
@State var data = ["Write a SwiftUI article"."Watch a WWDC video."."Order takeout."."Follow OldBirds public account"."Run 2 kilometers at 6:30.",]
var body: some View {
VStack {
List {
ForEach(0..<data.count) { index in
Text(data[index])
}
}
Button("Add to-do items") {
addTodo()
}.padding()
}
}
func addTodo(a) {
data.append("Hello SwiftUI")}}Copy the code
Click the button, the list does not respond. We then run the code into the emulator, click the button, and output the following error in the console:
ForEach<Range<Int>, Int, Text> count (6) ! = its initial count (5). `ForEach(_:content:)` should only be usedfor *constant* data. Instead conform data to `Identifiable` or use `ForEach(_:id:content:)` and provide an explicit `id`!
Copy the code
**ForEach(_:content) can only use constant data **.
Identifiable
In the to-do list case, we simply pass the array listData
What happens if we do not implement Identifiable Items?
As we know from the error, we must implement the Identifiable agreement on elements of arrays.
An array element of a normal type, such as String, will also report an error:
You can simply specify the id: \.self, since they are themselves recognizable objects.
So let’s talk a little bit more about ID.
id & Hashable
public init(_ data: Data.id: KeyPath<Data.Element.ID>, @ViewBuilder content: @escaping (Data.Element) - >Content)
Copy the code
The ID requires that we pass in a KeyPath, and the ID must implement Hashable. Int/String/… Both implement Hashable by default.
The initialization by specifying id applies to scenarios where the element does not implement Identifiable.
If TodoItem does not implement Identifiable, we can do the same:
But we need to make sure task is unique. Why?
TodoItem
Why implement Identifiable?
The purpose of implementing Identifiable is to separate the members of an array, allowing for more efficient management of views in a List.
Let’s use a simple example to illustrate the principle:
struct TodoItem {
var task: String
var imgName: String
}
struct ContentView: View {
@State var listData: [TodoItem] = [
TodoItem(task: "Write a SwiftUI article", imgName: "pencil.circle"),
TodoItem(task: "Watch a WWDC video.", imgName: "square.and.pencil"),
TodoItem(task: "Order out.", imgName: "folder"),
TodoItem(task: "Follow OldBirds public account", imgName: "link"),
TodoItem(task: "Run 2 kilometers at 6:30.", imgName: "moon"),]var body: some View {
VStack {
List {
ForEach(listData, id: \.task) { item in
HStack{
Image(systemName: item.imgName)
Text(item.task)
}
}
}
Button("Add to-do items") {
addTodo()
}.padding()
}
}
func addTodo(a) {
listData.append(TodoItem(task: "Write a SwiftUI article", imgName: "moon"))}}Copy the code
We leave TodoItem without implementing Identifiable, and pass it to ID using \.task. We want the list to refresh synchronously when we click the add button, and the new icon is a moon. But things don’t go your way:
You should also notice that the icon of the new view is not moon. Instead, I used pencil.circle from the first TodoItem(Task: “Write a SwiftUI article “, imgName:” Pencil. circle”). In fact, it wasn’t just the icon, the entire display was the same as the first view. Because the two TodoItems cannot be distinguished, the confusion occurs because they are repeated as the original view.
conclusion
ForEach is relatively easy to use. There are three ways to initialize ForEach. When iterating through a range, make sure that the set is constant. The second ensures element uniqueness through Identifiable, usually using UUID as the ID. The third way, by specifying KeyPath, is to ensure that Hashable is implemented and preferably unique, otherwise the display will be confused.