onDelete介绍
onDelete 方法通常用于 SwiftUI 的 ForEach 视图中,以便在列表中提供删除功能。它允许用户从列表中移除元素,通常与 List 和 ForEach 配合使用。在 ForEach 中,onDelete 方法会接受一个删除操作的闭包,允许根据用户的操作从数据源中删除相应的项目。
首先,我们需要知道的是,onDelete()是List提供的一个删除交互方式,它可以让用户通过滑动手势删除某一行对应的元素。
实际的删除代码如下:
func removeRows(at offsets: IndexSet) {
expenses.items.remove(atOffsets: offsets)
}
List {
// 使用 id: \.name
ForEach(expenses.items, id: \.name) { item in
Text(item.name)
}
.onDelete(perform: removeRows)
}
实现的流程为:
1、当用户在ForEach列表上滑动删除某一行时,Swift UI会捕获并识别出用户想要删除的内容,并且会将它的索引(IndexSet)传递给removeRows(at:)函数。
这里我们并没有显式的看到索引(IndexSet),这是因为它是在后台由.onDelete自动处理的。
2、removeRows(at:)函数被调用后,会接收用户想要删除的内容的索引,然后调取remove(atOffsets:)方法从数组中删除对应的项目。
1)首先,removeRows的at也是由后台.onDelete自动处理的,所以没有看到显示的传递。
2)其次是,IndexSet是Swift标准库中的一个类型,用于表示一组索引。它可以包含一个或多个整数值,通常用来标识集合或数组中的索引为止。在删除操作中,IndexSet可能标识需要删除的多个元素的位置。
func removeRows(at offsets: IndexSet) { }
3)remove(atOffsets:)是Swift UI提供的一个数组扩展方法,用于从数组中删除多个元素,传入的参数是IndexSet。
所以,假设我们的列表中要删除[2,4]索引的元素,那么首先Swift UI会识别删除的内容索引,并将[2,4]索引传递给.onDelete处理。onDelete会调取remove(atOffsets:)方法,删除对应的索引元素。
代码示例
示例1
假设我们有一个数组 books 来存储书籍的列表,可以在 ForEach 中使用 onDelete,让用户能够从列表中删除项目。
import SwiftUI
struct ContentView: View {
@State private var books = ["Book 1", "Book 2", "Book 3"]
var body: some View {
NavigationView {
List {
ForEach(books, id: \.self) { book in
Text(book)
}
.onDelete(perform: deleteBooks) // 调用删除方法
}
.navigationTitle("Books")
.toolbar {
EditButton() // 提供编辑按钮
}
}
}
// 删除方法
func deleteBooks(at offsets: IndexSet) {
books.remove(atOffsets: offsets) // 从数组中删除项目
}
}
在 SwiftUI 中,onDelete 方法为 ForEach 提供了从数据源中删除元素的功能,通常用于列表视图中的行删除。以下是 onDelete 的数据传递和流程详解:
1、数据源定义
首先,定义一个支持删除的数据源。在这个例子中,我们用 @State 属性包装一个可变数组 books,以便在删除时能够更新视图。
@State private var books = ["Book 1", "Book 2", "Book 3", "Book 4"]
2、ForEach 视图和 .onDelete 方法
在 List 内部使用 ForEach 循环遍历数据源。ForEach 本身不支持删除功能,但 onDelete 方法可以添加给它。
ForEach(books, id: \.self):ForEach 遍历 books 数组中的每个元素,将其唯一标识符(如 id: \.self)提供给 SwiftUI 来识别每一行。
.onDelete(perform: deleteBooks):onDelete 方法将一个删除操作的闭包传递给 ForEach,该闭包会在用户执行删除操作时调用。
ForEach(books, id: \.self) { book in
Text(book)
}
.onDelete(perform: deleteBooks)
3、onDelete 的触发方式
为了触发 onDelete,通常在 List 的 .toolbar 中添加一个 EditButton:
.toolbar {
EditButton() // 让用户可以进入编辑模式
}
当用户点击 EditButton 时,列表会进入编辑模式,行左侧会出现删除按钮,允许用户滑动删除或点击删除按钮触发 onDelete。
4、deleteBooks 方法的实现
deleteBooks 方法是 onDelete 方法的核心部分。它接受一个 IndexSet 参数,即用户在界面上选择删除的行的索引。以下是 deleteBooks 方法的定义及其数据传递过程:
func deleteBooks(at offsets: IndexSet) {
books.remove(atOffsets: offsets) // 从 books 数组中删除选定的元素
}
offsets: IndexSet:IndexSet 表示用户选择的删除项目在数组中的位置集合。当用户滑动或点击删除按钮,SwiftUI 会自动生成一个 IndexSet,并将其传递给 deleteBooks 方法。
remove(atOffsets: offsets):remove(atOffsets:) 是 Swift 的数组方法,可以根据 IndexSet 删除对应索引的元素。因为 books 是一个 @State 属性包装的数组,删除操作会自动触发视图更新,使删除后的列表重新渲染。
执行流程总结
1、点击 EditButton:用户点击 EditButton 进入编辑模式,列表左侧出现删除按钮。
2、触发 onDelete:用户滑动删除某一行或点击删除按钮,SwiftUI 会自动调用 onDelete 的闭包 deleteBooks。
3、IndexSet 数据传递:onDelete 方法生成一个包含要删除项目索引的 IndexSet,并将其传递给 deleteBooks 方法。
4、更新数据源并重新渲染:在 deleteBooks 中,通过 remove(atOffsets:) 从 books 数组中删除对应索引的数据。@State 包装的 books 数组更新后,SwiftUI 自动重新渲染列表,显示删除后的数据。
注意事项
IndexSet 是删除的核心数据。它支持多行删除,可以包含多个索引。
删除操作影响视图更新,需用 @State 属性包装数组,才能触发 UI 变化。
示例2
onDelete方法同样适用于ForEach中的SwiftData元素的删除。
struct ContentView: View {
func deleteBooks(at offsets: IndexSet) {
print("offsets:\(offsets)")
for offset in offsets {
print("offset:\(offset)")
// find this book in our query
let book = books[offset]
// delete it from the context
modelContext.delete(book)
}
}
var body: some View {
NavigationStack {
List {
ForEach(books) { book in
NavigationLink(value: book) {
...
}
}
.onDelete(perform: deleteBooks)
}
.navigationTitle("Bookworm")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
EditButton()
}
}
}
}
}
在删除SwiftData元素时,与前者示例不同的是delete方法的内容。在这段代码中,deleteBooks 方法使用 SwiftData 的 modelContext 来删除 books 列表中的元素。
1、deleteBooks方法的定义
func deleteBooks(at offsets: IndexSet) {
print("offsets:\(offsets)")
for offset in offsets {
print("offset:\(offset)")
// find this book in our query
let book = books[offset]
// delete it from the context
modelContext.delete(book)
}
}
参数 offsets:deleteBooks 方法接受一个 IndexSet 参数 offsets,它表示用户选择删除的项目在 books 数组中的索引位置。当用户在列表中滑动删除一行时,SwiftUI 会自动生成 IndexSet 并传递给 deleteBooks。
2、遍历 offsets 并定位要删除的 Book
for offset in offsets {
let book = books[offset]
modelContext.delete(book)
}
遍历 offsets:因为 IndexSet 可以包含多个索引,代码通过 for offset in offsets 循环遍历每一个索引。
查找 book:在循环内部,通过 books[offset] 来定位要删除的 Book 实例。offset 是 books 数组的一个索引,因此 books[offset] 表示 books 数组中与当前索引对应的 Book 对象。
3、从 modelContext 中删除 book
modelContext.delete(book)
删除 book:通过 modelContext.delete(book) 方法,将定位到的 Book 实例从数据上下文中删除。modelContext 是一个 SwiftData 的数据管理上下文对象,负责管理数据模型的生命周期和持久化。
触发更新:在 modelContext 删除了 book 实例后,SwiftData 会自动更新并重新渲染 books 数组的 UI 视图,使删除操作立即生效,用户在界面上看到更新后的列表。
总结删除流程
1、用户滑动删除某一行,触发 onDelete 方法。
2、onDelete 将触发 deleteBooks(at:) 方法,offsets 参数中包含要删除行的索引。
3、deleteBooks 遍历 offsets,根据每个索引定位到 books 数组中的具体 Book 对象。
4、modelContext.delete(book) 将选中的 Book 对象从数据上下文中删除,触发 SwiftData 的数据更新。
5、books 数组被更新,SwiftUI 重新渲染列表以反映删除后的状态。
这样,deleteBooks 方法就通过 modelContext 的删除操作,将特定元素从数据源中移除并反映到用户界面上。