在前面的文章中,我们讲解了 SwiftData 的数据模型的定义、数据的简单查询以及数据的修改和删除。在这篇文章里,我们对查询进行更加详细的讲解,比如如何进行查询数据的排序等。
查询数据排序
对 SwiftData 查询进行排序的简单方法是向 @Query宏传递额外的参数。例如,我们可能想按名称的字母顺序对 person 实例进行排序,可以用下面的代码:
@Query(sort: \Person.name) var sortedPersons: [Person]
上面的代码查询出来的数组结果就是以 name 字段进行排序的。当然,我们不仅可以通过字段进行升序,还可以通过 order 参数进行逆序,比如通过 level 字段进行逆序:
@Query(sort: \Person.level, order: .reverse) var levelReversePersons: [Person]
上面的代码示例是只处理一个属性,但如果你需要多个属性:比如你想按 level 降序排序,然后按名字升序排序,你需要使用一个 SortDescriptor数组:
@Query(sort: [SortDescriptor(\Person.level, order: .reverse), SortDescriptor(\Person.name)]) var multipleCondiSortPersons: [Person]
动态排序
你可以在该数组中拥有任意数量的排序描述符,SwiftData将逐一处理它们。当你在编译时就知道你的排序顺序时,这种方法效果很好,但通常我们希望用户能够按照他们想要的方式对数据进行排序。
这需要更多的工作,因为用 @Query 创建的属性没有任何我们可以使用的简单 sortOrder 属性。所以,我们需要将 @Query 属性放到 SwiftUI 中的一个子视图中,这样我们就可以使用视图的初始化器注入排序。
第一步是创建一个名为 PersonListView 的新 SwiftUI 视图,然后在顶部导入 SwiftData 框架。
接下来,我们需要将一些代码从 ContentView移动到 PersonListView,代码如下:
import SwiftUI
import SwiftData
struct PersonListView: View {
@Query var persons: [Person]
@Environment(\.modelContext) var modelContext
var body: some View {
List {
ForEach(persons) { person in
NavigationLink(value: person) {
VStack(alignment: .leading) {
Text(person.name)
.font(.headline)
Text(person.address)
}
}
}
.onDelete(perform: deletePerson)
}
}
func deletePerson(indexSet: IndexSet) {
for index in indexSet {
let person = persons[index]
modelContext.delete(person)
}
}
}
然后我们需要将 ContentView 的代码修改如下:
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) var modelContext
var body: some View {
NavigationStack {
PersonListView()
.navigationDestination(for: Person.self, destination: EditPersonView.init)
.navigationTitle("SwiftDataDemo")
.toolbar { Button("添加数据", action: addData) }
}
}
func addData() {
let jack = Person(name: "Jack", address: "Street-1")
let rose = Person(name: "Rose", address: "Street-2")
modelContext.insert(jack)
modelContext.insert(rose)
}
}
做完上面的修改,我们运行代码的显示和逻辑应该跟之前一模一样。接着,我们就可以来操作用户动态排序了,这需要以下五个步骤:
- 创建变量来保存用户当前的排序顺序。
- 通过 UI 以根据用户的设置调整排序顺序。
- 告诉 PersonListView 它需要以某种排序顺序创建。
- 更新其预览以传递示例排序顺序。
- 在创建 PersonListView 时将排序顺序传递给它。
下面我们来一步一步实现上面的五个步骤。
首先,将下面的属性添加到 ContentView 中,它将包含具有合理默认值的当前排序顺序:
@State private var sortOrder = SortDescriptor(\Person.name)
接着,我们需要在工具栏中创建一个菜单按钮,让用户在排序顺序之间切换。将下面的代码添加到 ContentView 中:
.toolbar {
Menu("排序", systemImage: "arrow.up.arrow.down") {
Picker("排序", selection: $sortOrder) {
Text("Name")
.tag(SortDescriptor(\Person.name))
Text("Level")
.tag(SortDescriptor(\Person.level, order: .reverse))
Text("Age")
.tag(SortDescriptor(\Person.age, order: .reverse))
}
.pickerStyle(.inline)
}
}
效果图如下:
第三步,我们需要向 PersonListView 试图添加一个初始化器,以便它接受用户自定义排序顺序来使用其查询。代码如下:
init(sort: SortDescriptor<Person>) {
_persons = Query(sort: [sort])
}
上面的代码添加完会发现预览报错了,这是因为在预览中我们没有传入初始化参数,预览代码修改如下:
#Preview {
PersonListView(sort: SortDescriptor(\Person.name))
}
最后一步,我们需要调整 ContentView 的代码,使其将排序值传递给 PersonListView,如下所示:
NavigationStack {
PersonListView(sort: sortOrder)
...
}
这样我们就完成了所有的步骤,现在就可以支持用户动态对结果进行排序。