SwiftData 学习笔记:增删改查

2,899 阅读3分钟

SwiftData 学习笔记:增删改查

前言

在上一篇文章中,我们简单介绍了 SwiftData 的使用场景,以及举了一个非常简单使用示例。在本篇文章中,我们将进行更加详细的介绍 SwiftData 的增删改查操作。

对于增删改查来说,最简单的应该就是查询的操作。所以我们先从查询开始。

查询

一句代码就能实现查询:

@Query private var items: [Item]

@Query 是非常强大的,当视图出现的时候它会立即从 SwiftData 中加载所有的缓存的数据模型。而且它会监测数据库的所有改变,当你对缓存的对象进行添加、删除、修改操作时,你获取的 items 也会进行同步更新。

删除

你可以通过对你的模型上下文调用 delete() 函数,来进行对 SwiftData 存储对象的删除。

示例代码如下:

@Model
final class Item {
    var timestamp: Date
    var name: String
    
    init(timestamp: Date, name: String) {
        self.timestamp = timestamp
        self.name = name
    }
}

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Item]

    var body: some View {
        NavigationSplitView {
            List {
                ForEach(items) { item in
                    let text = "Item at \(item.timestamp), name: \(item.name)"
                    
                    NavigationLink {
                        Text(text)
                    } label: {
                        Text(text)
                    }
                }
                .onDelete(perform: deleteItems)
            }
        } detail: {
            Text("Select an item")
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            for index in offsets {
                modelContext.delete(items[index])
            }
        }
    }
}

在上述代码中,我们使用 ForEach 去迭代 SwiftData 查询出的所有数据。因此,我们现在可以使用 SwiftUI 编写与任何数据数组相同的删除方法。

最后,在 ForEach 后面加上 OnDelete() 修饰符就可以了:

.onDelete(perform: deleteItems)

这样,我们就完成了删除操作。删除操作完事之后,接下来我们看一下如何进行修改操作。

修改

我们需要编写一个详情页来进行 Item 的修改操作。新建一个名为 ItemDetailView 的 SwiftUI 文件,导入 SwiftData:

struct ItemDetailView: View {
    @Bindable var item: Item
    
    var body: some View {
        Form {
            TextField("Name", text: $item.name)
        }
        .navigationTitle("Edit Item")
    }
}

#Preview {
    do {
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let example = Item(timestamp: Date(), name: "Default")
        return ItemDetailView(item: example)
            .modelContainer(container)
    } catch {
        fatalError("Error")
    }
}

首先,我们需要声明一个属性用来接收外界传递进来的数据:item。因为我们是需要在详情页修改首页传递进来的数据,并且首页要同步这个修改。所以我们需要用 @Bindable 修饰 item

接着,我们在 body 中使用 Form 包裹一下 TextField 控件,这样页面的布局会比较美观,并没有别的逻辑作用。然后使用 TextField 来进行 name 的修改。

这样,我们的逻辑代码已经写完了。但 Preview 还需要我们处理一下,它是编译不过的。因为 SwiftData 不知道在哪里创建数据,这里并没有可用的模型容器或上下文。预览页面的代码分下面四步:

  • 创建自定义 ModelConfiguration 对象,并指定只要内存缓存。
  • 使用创建的 config 来创建模型容器。
  • 创建一个 Item 类型的示例对象。
  • 将创建的模型容器和示例对象传递给 ItemDetailView

Tips:如果你创建的 SwiftData 模型对象并没有被模型容器所包含,你的预览页面会崩溃。

最后,再将 ContentView 的代码修改如下即可:

List {
    ForEach(items) { item in
        let text = "Item at \(item.timestamp), name: \(item.name)"
        
        NavigationLink {
            ItemDetailView(item: item) // 此处代码
        } label: {
            Text(text)
        }
    }
    .onDelete(perform: deleteItems)
}

这就完成了修改操作的全部改动。此时运行项目,在详情页的修改之后,再返回首页你会看到 name 会被同步修改。

添加

添加跟删除操作很类似:

private func addItem() {
    let newItem = Item(timestamp: Date(), name: "default")
    modelContext.insert(newItem)        
}

新建一个 Item 类型的对象,然后调用模型上下文(modelContext)的 insert 函数将其传递进去即可。

Button(action: addItem) {
    Label("Add Item", systemImage: "plus")
}

然后将其当做给按钮的点击事件即可。