onDelete 方法通常用于 SwiftUI 的 ForEach 视图中,以便在列表中提供删除功能。它允许用户从列表中移除元素,通常与 List 和 ForEach 配合使用。

在 ForEach 中,onDelete 方法会接受一个删除操作的闭包,允许根据用户的操作从数据源中删除相应的项目。
.onDelete(perform: (IndexSet) -> Void)
用户通过滑动手势删除某一行对应的元素,SwiftUI将要删除的元素在当前ForEach数据源中的索引集合传递给 IndexSet,根据 IndexSet 从当前ForEach实际绑定的底层数据源中移除对应元素。
基本用法
ForEach添加onDelete方法,定义对应的删除方法。
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)
}
用户在ForEach列表滑动删除时,SwiftUI会捕获并识别出用户想要删除的内容,将索引(IndexSet)传递给对应的方法(这里传递给removeRows方法)。
传递的方法需要接收一个IndexSet类型的参数,这个参数是SwiftUI隐式传递给删除的方法,因此在onDelete中没有看到显式传递索引。
IndexSet是Swift标准库中的一个类型,用于表示一组索引。它可以包含一个或多个整数值,通常用来标识集合或数组中的索引为止。在删除操作中,IndexSet可能标识需要删除的多个元素的位置。
func removeRows(at offsets: IndexSet) { }
定义删除方法(这里是removeRows),接收传入的IndexSet参数,并从数组中删除对应的元素。
假设列表中要删除[2,4]索引的元素,那么首先Swift UI会识别删除的内容索引,并将[2,4]索引传递给.onDelete处理。onDelete会调取remove(atOffsets:)方法,删除对应的索引元素。
注意事项
1、IndexSet 指的是当前 ForEach 的数据源索引,不是全局数组或其它计算结果的索引。
2、.onDelete 必须放在对应那个 ForEach 上(或该 ForEach 的链式调用中),否则 offsets 会与数据不匹配。
3、如果 ForEach 使用的是 identifiable 数据源,优先直接删除对应的模型对象而非按索引操作(更稳健)。
4、当从数组删除多个元素时,按索引从大到小删除(或直接使用 remove(atOffsets:)),避免索引错位。
5、不要对计算属性数组(例如 filterRecords、group.records 的副本)直接用 offsets 去删除原始数据——这会导致偏移或删除错误。必须把删除映射回“真实的”可变数据源(source of truth)。
6、IndexSet 是删除的核心数据。它支持多行删除,可以包含多个索引。
总结
onDelete方法通常配合ForEach实现对象的删除。
onDelete需要传入一个删除方法,当用户滑动删除时,SwiftUI会将待删除的对象传递给删除方法,删除方法根据IndexSet索引,删除对应的元素。
如果想要更灵活的删除UI,可以使用swipeActions替代onDelete,这也可以直接删除对象,并且避免索引混淆。
ForEach(items) { item in
Text(item.name)
.swipeActions {
Button(role: .destructive) {
delete(item)
} label: {
Label("删除", systemImage: "trash")
}
}
}
相关文章
1、Swift列表项滑动手势swipeActions:https://fangjunyu.com/2024/10/27/swift%e5%88%97%e8%a1%a8%e9%a1%b9%e6%bb%91%e5%8a%a8%e6%89%8b%e5%8a%bfswipeactions/
2、SwiftUI多选删除列表元素:https://fangjunyu.com/2024/11/07/swiftui%e5%a4%9a%e9%80%89%e5%88%a0%e9%99%a4%e5%88%97%e8%a1%a8%e5%85%83%e7%b4%a0/
3、SwiftUI切换视图编辑模式的EditButton: https://fangjunyu.com/2024/12/09/swiftui%e5%88%87%e6%8d%a2%e8%a7%86%e5%9b%be%e7%bc%96%e8%be%91%e6%a8%a1%e5%bc%8f%e7%9a%84editbutton/
扩展知识
1、删除复杂视图对象
嵌套ForEach以及Section分组的View视图:
List {
ForEach(groupedRecords, id:\.date) { group in
Section(header: groupRecordsHeader(group: group,collapsedDates: $collapsedDates)) {
if !collapsedDates.contains(group.date) {
ForEach(group.records, id:\.self) { item in
SavingsRecordRow(record: item)
}
.onDelete { offsets in
removeGroupedRows(offsets, from: group.records)
}
}
}
}
}
如果需要删除对应元素,必须将onDelete方法放在最内部的ForEach上,offsets才能对应到具体的索引。如果放在最外面的ForEach,就会发生不可预测的删除行为。
内部ForEach的索引只能对应Group组的对象,因此需要在Group组中使用索引映射到对应的对象。
这里将onDelete改为闭包形式,将IndexSet索引和Group组传递到删除方法。
func removeGroupedRows(_ offsets: IndexSet, from record: [SavingsRecord]) {
let itemToRemove = offsets.map { record[$0] }
for item in itemToRemove {
modelContext.delete(item)
}
try? modelContext.save()
}
在删除方法中,通过map从Group组中匹配对应的对象,然后进行删除操作。
2、多选删除对象
如果需要选择多个对象并进行删除操作,可以使用EditButton() 切换为编辑视图。
.toolbar {
EditButton() // 让用户可以进入编辑模式
}
当用户点击EditButton 时,列表会进入编辑模式,行左侧会出现删除按钮,允许用户滑动删除或点击删除按钮触发 onDelete。
也可以自定义多选逻辑,通过删除方法删除对象。
