SwiftUI删除元素的onDelete方法
SwiftUI删除元素的onDelete方法

SwiftUI删除元素的onDelete方法

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 的删除操作,将特定元素从数据源中移除并反映到用户界面上。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注