SwiftData:对搜索结果进行排序

345 阅读3分钟

在前面的文章中,我们讲解了 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)
    }
}

效果图如下: 截屏2024-11-24 16.01.55.png

第三步,我们需要向 PersonListView 试图添加一个初始化器,以便它接受用户自定义排序顺序来使用其查询。代码如下:

init(sort: SortDescriptor<Person>) {
    _persons = Query(sort: [sort])
}

上面的代码添加完会发现预览报错了,这是因为在预览中我们没有传入初始化参数,预览代码修改如下:

#Preview {
    PersonListView(sort: SortDescriptor(\Person.name))
}

最后一步,我们需要调整 ContentView 的代码,使其将排序值传递给 PersonListView,如下所示:

NavigationStack {
    PersonListView(sort: sortOrder)
    ...
}

这样我们就完成了所有的步骤,现在就可以支持用户动态对结果进行排序。