SwiftUI是一個新的框架,可以讓我們以聲明性的方式構建用戶界面。在SwiftUI中,可以使用ForEach視圖來創建動態視圖列表。ForEach是一種非常有用的視圖類型,特別是當您需要用相同的方式處理一系列數據時。在本文中,我們將從多個方面對SwiftUI中的ForEach進行詳細闡述。
一、ForEach的基礎知識
ForEach是SwiftUI中用於構建動態列表的一種視圖類型。與傳統的iOS開發不同,SwiftUI採用聲明性編程範式。這意味著我們告訴框架我們需要什麼,然後讓它自己進行處理。ForEach採用一個可迭代的數據源作為輸入,然後將其映射到相應的視圖層次結構中。
下面是一個ForEach的基本示例,它使用數組來構建一個簡單的列表:
struct ContentView: View {
let names = ["Alice", "Bob", "Charlie", "Dave"]
var body: some View {
VStack {
ForEach(names, id: \.self) { name in
Text(name)
}
}
}
}
在上面的代碼中,我們將字元串數組傳遞給ForEach構造函數。id參數用於指定每個元素的唯一標識符。在這種情況下,我們使用自身。ForEach迭代數組中的每個元素,並為每個元素創建一個Text視圖。然後,我們將整個列表包裝在一個垂直堆棧中,以便它們垂直排列。
二、ForEach中的數據操作
SwiftUI中的ForEach提供了一些方便的方法來操作數據源。下面是常用的一些方法:
1、添加元素
我們可以使用Swift數組的append方法添加新元素。在更新模型後,視圖將自動調整以反映更改。
struct Student {
var name: String
}
struct ContentView: View {
@State private var students = [
Student(name: "Alice"),
Student(name: "Bob"),
Student(name: "Charlie")
]
var body: some View {
VStack {
ForEach(students, id: \.name) { student in
Text(student.name)
}
Button("Add student") {
self.students.append(Student(name: "Dave"))
}
}
}
}
2、刪除元素
與添加元素類似,我們可以使用Swift數組的remove方法刪除元素。更新視圖和模型的方法與添加元素相同。
struct ContentView: View {
@State private var students = [
Student(name: "Alice"),
Student(name: "Bob"),
Student(name: "Charlie")
]
var body: some View {
VStack {
ForEach(students, id: \.name) { student in
Text(student.name)
}
Button("Remove student") {
self.students.remove(at: 0)
}
}
}
}
3、移動元素
移動元素需要兩個索引:要移動的元素的當前位置和要將它移動到的新位置。
struct ContentView: View {
@State private var students = [
Student(name: "Alice"),
Student(name: "Bob"),
Student(name: "Charlie")
]
var body: some View {
VStack {
ForEach(students, id: \.name) { student in
Text(student.name)
}
Button("Move student") {
students.move(fromOffsets: IndexSet([0]), toOffset: 2)
}
}
}
}
三、ForEach的性能優化
SwiftUI使用了一種稱為「Diffing」的演算法來比較最新數據源與上一個版本的數據源,並確定需要更新的視圖。在內部,SwiftUI會使用SwiftEquatable協議進行比較。
對於ForEach,我們可以使用id參數來告訴SwiftUI如何比較數據源中的元素。默認情況下,ForEach使用元素的內存地址作為標識符。如果數據源中的元素沒有遵循SwiftEquatable協議,則默認行為可能不會按預期工作。通過傳遞一個可用於比較元素的鍵路徑,我們可以自定義標識符。
struct Student: Identifiable {
let id = UUID()
var name: String
}
struct ContentView: View {
@State private var students = [
Student(name: "Alice"),
Student(name: "Bob"),
Student(name: "Charlie")
]
var body: some View {
VStack {
ForEach(students) { student in
Text(student.name)
}
Button("Add student") {
self.students.append(Student(name: "Dave"))
}
Button("Remove student") {
self.students.remove(at: 0)
}
Button("Move student") {
students.move(fromOffsets: IndexSet([0]), toOffset: 2)
}
}
}
}
1、使用Equatable來比較元素
首先,讓我們看一下在沒有指定id參數的情況下ForEach的默認行為:
struct Student {
var name: String
}
struct ContentView: View {
@State private var students = [
Student(name: "Alice"),
Student(name: "Bob"),
Student(name: "Charlie")
]
var body: some View {
VStack {
ForEach(students) { student in
Text(student.name)
}
Button("Add student") {
self.students.append(Student(name: "Dave"))
}
Button("Remove student") {
self.students.remove(at: 0)
}
}
}
}
當我們向上面的列表添加或刪除學生時,我們會注意到,在刪除學生時,視圖滯後於模型。這是因為SwiftUI需要在模型中查找要刪除的學生,而由於它們沒有唯一的標識符,因此必須一一比較。這可能在數據集很大時變得非常慢。
為了解決這個問題,我們可以將Student標記為Equatable,並讓SwiftUI使用SwiftEquatable協議進行比較。雖然這不是必需的,但它可以使性能更好。
struct Student: Equatable {
var name: String
}
struct ContentView: View {
@State private var students = [
Student(name: "Alice"),
Student(name: "Bob"),
Student(name: "Charlie")
]
var body: some View {
VStack {
ForEach(students) { student in
Text(student.name)
}
Button("Add student") {
self.students.append(Student(name: "Dave"))
}
Button("Remove student") {
self.students.remove(at: 0)
}
}
}
}
2、使用標識符來比較元素
如果我們的數據對象沒有適合使用Equatable協議比較的屬性,我們可以使用ForEach的id參數來指定一個鍵路徑來比較元素。
struct Student {
var name: String
var id: Int
}
struct ContentView: View {
@State private var students = [
Student(name: "Alice", id: 1),
Student(name: "Bob", id: 2),
Student(name: "Charlie", id: 3)
]
var body: some View {
VStack {
ForEach(students, id: \.id) { student in
Text(student.name)
}
Button("Add student") {
self.students.append(Student(name: "Dave", id: 4))
}
Button("Remove student") {
self.students.remove(at: 0)
}
}
}
}
在上面的代碼中,我們將id參數設置為我們可以使用的鍵路徑(student.id)。
四、ForEach和可選類型
當我們處理可選類型時,我們需要使用ForEach的if語句來避免nil對象造成的崩潰。下面是一個使用可選類型的示例:
struct ContentView: View {
@State private var names = ["Alice", "Bob", nil, "Charlie"]
var body: some View {
VStack {
ForEach(names, id: \.self) { name in
if let name = name {
Text(name)
}
}
Button("Add name") {
self.names.append("Dave")
}
}
}
}
在上面的代碼中,我們向名字數組中添加了一個空值。當我們構建ForEach時,我們使用if語句過濾掉了nil對象,並只創建非空Text視圖。
原創文章,作者:MDHGR,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/361503.html