在 SwiftData(Apple 最新的数据持久化框架)中,可以通过 Filtering(过滤)和 Sorting(排序)对数据进行查询和组织。这些操作在 SwiftData 的 @Query
属性包装器中得到了优雅且易用的处理。
以下我将通过一些常用的过滤和排序操作演示如何使用 SwiftData 有效地管理数据。
前置理解 #
SwiftData 的核心组件:
Model
: 数据模型使用@Model
注解来定义。@Query
: 一个强大的属性包装器,用于通过 SwiftData 管理和查询数据。Predicate 和 SortDescriptor
: 在过滤和排序中,这些对象用于定义查询条件和排序规则。
过滤和排序的目标:
- Filtering:通过条件限制查询结果,例如查找指定属性值、范围或符合谓词的数据。
- Sorting:以特定的顺序排序查询结果,例如升序、降序或自定义比较逻辑。
1. 定义 @Model
数据模型
#
在 SwiftData 中,所有数据模型都需要用 @Model
注解定义。以下我们定义一个简单的任务模型:
import SwiftData
@Model
class Task {
var id: UUID
var title: String
var isCompleted: Bool
var dueDate: Date
init(title: String, isCompleted: Bool, dueDate: Date) {
self.id = UUID()
self.title = title
self.isCompleted = isCompleted
self.dueDate = dueDate
}
}
说明:
Task
模型代表一个任务,包含以下属性:id
: 唯一标识符。title
: 任务标题。isCompleted
: 是否已完成。dueDate
: 任务截止日期。
之后我们会对这些任务数据进行 过滤 和 排序。
2. Filtering 的例子 #
例子 1: 基于布尔属性的过滤 #
从数据中筛选出所有未完成的任务:
@Query(filter: #Predicate { $0.isCompleted == false })
private var pendingTasks: [Task]
- 用法:
- 使用
#Predicate
构建过滤条件。 $0.isCompleted == false
表示过滤出isCompleted
属性为false
的数据。
- 使用
例子 2: 按字符串部分匹配过滤(模糊搜索) #
筛选出任务标题包含关键字 “important” 的任务:
@Query(filter: #Predicate { $0.title.contains("important") })
private var importantTasks: [Task]
- 说明:
- 使用字符串方法
.contains()
进行部分匹配。 - 确保模糊搜索对大小写敏感。如果需要大小写不敏感,请使用
.localizedCaseInsensitiveContains()
。
- 使用字符串方法
例子 3: 基于时间范围过滤 #
筛选出未来 7 天到期的任务:
@Query(filter: #Predicate {
let nextWeek = Calendar.current.date(byAdding: .day, value: 7, to: Date())
return $0.dueDate <= nextWeek && $0.dueDate >= Date()
})
private var tasksDueNextWeek: [Task]
- 说明:
- 使用
Calendar
和Date
计算未来 7 天的日期范围。 - 通过日期比较
<=
和>=
过滤符合条件的任务。
- 使用
例子 4: 组合多个条件 #
筛选出未来任务(未完成任务且在一周内到期):
@Query(filter: #Predicate {
let nextWeek = Calendar.current.date(byAdding: .day, value: 7, to: Date())
return $0.isCompleted == false && $0.dueDate <= nextWeek
})
private var futureTasks: [Task]
- 组合条件:通过
&&
或||
实现多个条件的组合。
例子 5: 动态过滤 #
可以根据用户输入动态调整条件,比如通过任务标题关键字搜索:
struct SearchView: View {
@State private var searchText = ""
@Query var filteredTasks: [Task]
var body: some View {
VStack {
TextField("Search tasks", text: $searchText)
.onChange(of: searchText) { text in
$filteredTasks = Query(Task.self, filter: #Predicate {
$0.title.contains(text)
})
}
List(filteredTasks) { task in
Text(task.title)
}
}
}
}
- 使用动态条件:
- 动态更新
@Query
的filter
。 - 基于用户输入的
searchText
创建过滤条件。
- 动态更新
3. Sorting 的例子 #
例子 1: 基于单个属性排序 #
将任务按照截止日期升序排列:
@Query(sort: #SortDescriptor(\.dueDate))
private var tasksSortedByDueDate: [Task]
- 说明:
- 使用
SortDescriptor
指定排序规则。 \.dueDate
表示对dueDate
属性进行排序。
- 使用
例子 2: 降序排列 #
将任务按截止日期降序排列:
@Query(sort: #SortDescriptor(\.dueDate, order: .reverse))
private var recentTasks: [Task]
- 用法:
- 在
#SortDescriptor
中设置order: .reverse
,将排序规则切换为降序。
- 在
例子 3: 多重排序 #
如果需要按多个属性排序,可以使用多个 SortDescriptor
:
@Query(sort: [
#SortDescriptor(\.isCompleted), // 未完成的任务优先
#SortDescriptor(\.dueDate) // 截止日期升序排列
])
private var prioritizedTasks: [Task]
- 说明:
- 第一排序规则:
isCompleted
,优先排列未完成任务。 - 第二排序规则:
dueDate
,相同完成状态时按截止日期升序排列。
- 第一排序规则:
例子 4: 动态排序 #
结合用户选择,动态切换不同的排序规则:
struct TasksView: View {
@State private var sortOption: String = "Due Date"
@Query var sortedTasks: [Task]
var body: some View {
Picker("Sort by", selection: $sortOption) {
Text("Due Date").tag("Due Date")
Text("Title").tag("Title")
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: sortOption) { option in
$sortedTasks = Query(
Task.self,
sort: option == "Due Date" ? #SortDescriptor(\.dueDate) : #SortDescriptor(\.title)
)
}
List(sortedTasks) { task in
Text(task.title)
}
}
}
- 说明:
- 动态更新排序规则。
- 使用用户选择控制
@Query
的排序方式。
4. Filtering 和 Sorting 结合使用 #
多条件过滤与排序可以联合使用。例如,筛选未完成的任务并按截止日期升序排列:
@Query(
filter: #Predicate { $0.isCompleted == false },
sort: #SortDescriptor(\.dueDate)
)
private var prioritizedIncompleteTasks: [Task]
5. 自定义方式通过代码查询 #
在某些特定情况下,你可能需要通过编程方式(非属性包装器)直接查询和使用过滤与排序。
func fetchIncompleteTasks(in context: ModelContext) -> [Task] {
do {
let results = try context.fetch(Query(
filter: #Predicate { $0.isCompleted == false },
sort: #SortDescriptor(\.dueDate)
))
return results
} catch {
print("Error fetching tasks: \(error)")
return []
}
}
总结 #
- SwiftData 使用
@Query
简化了常见的 Filtering 和 Sorting 操作。 - 过滤(Filtering) 是通过
#Predicate
定义条件实现,比如部分匹配或日期范围。 - 排序(Sorting) 是通过
#SortDescriptor
定义规则控制顺序,可支持单一或多重排序。 - 结合动态过滤和排序,开发者可以实现更灵活的、用户可控的数据查询功能。
通过这些示例,您可以轻松构建复杂的业务逻辑。
在 SwiftUI 中,@Query
属性包装器与 FetchDescriptor
的结合使用,是 SwiftData 框架下实现数据驱动 UI 的核心模式。以下是具体实践和原理分析:
场景 #
动态更新 @Query
的查询条件
#
与 FetchDescriptor #
一、基础概念与作用 #
@Query
的作用@Query
是 SwiftData 提供的属性包装器,自动管理数据查询与界面更新。当底层数据变化时,其修饰的数组(如[Item]
)会自动触发关联视图的刷新。FetchDescriptor
的功能FetchDescriptor
用于定义数据查询的条件,包括排序规则、过滤条件(Predicate)、分页设置等,类似于 Core Data 的NSFetchRequest
。
二、结合使用步骤与代码示例 #
步骤 1:声明 @Query
属性
#
在视图中通过 @Query
声明需要观察的数据集合,并直接绑定 FetchDescriptor
:
struct EventListView: View {
// 使用 @Query 绑定 FetchDescriptor
@Query(FetchDescriptor<Item>(sortBy: [SortDescriptor(\.timestamp)]))
private var items: [Item]
var body: some View {
List(items) { item in
Text(item.timestamp.formatted())
}
}
}
说明:此处 FetchDescriptor
定义了按 timestamp
排序的查询条件,@Query
自动执行查询并监听数据变化。
步骤 2:动态修改查询条件 #
通过视图初始化参数传递动态 FetchDescriptor
:
struct FilteredListView: View {
let filterKeyword: String
// 动态生成 FetchDescriptor
private var descriptor: FetchDescriptor<Item> {
let predicate = #Predicate<Item> { $0.title.contains(filterKeyword) }
return FetchDescriptor(predicate: predicate)
}
@Query(descriptor)
private var filteredItems: [Item]
var body: some View { /* ... */ }
}
说明:通过计算属性动态生成包含关键词过滤条件的 FetchDescriptor
,实现动态数据筛选。
三、高级用法与最佳实践 #
1. 跨视图共享查询逻辑 #
将 FetchDescriptor
封装为静态属性以复用:
extension FetchDescriptor where T == Item {
static var recentItems: FetchDescriptor<Item> {
let predicate = #Predicate<Item> { $0.timestamp > Date().addingTimeInterval(-86400) }
return FetchDescriptor(predicate: predicate, sortBy: [SortDescriptor(\.timestamp)])
}
}
// 在视图中引用
@Query(.recentItems) private var recentItems: [Item]
2. 结合 @Environment
注入动态参数
#
通过环境变量控制查询条件:
struct UserScopeView: View {
@Environment(\.userID) private var userID
private var descriptor: FetchDescriptor<Item> {
let predicate = #Predicate<Item> { $0.ownerID == userID }
return FetchDescriptor(predicate: predicate)
}
@Query(descriptor) private var userItems: [Item]
var body: some View { /* ... */ }
}
3. 性能优化:分页与批量加载 #
// 定义分页 FetchDescriptor
var pagedDescriptor: FetchDescriptor<Item> {
var descriptor = FetchDescriptor<Item>(sortBy: [SortDescriptor(\.timestamp)])
descriptor.fetchLimit = 20 // 每页 20 条
descriptor.fetchOffset = currentPage * 20
return descriptor
}
@Query(pagedDescriptor) private var pagedItems: [Item]
四、常见问题与调试 #
谓词(Predicate)语法验证
SwiftData 的#Predicate
宏在编译时检查语法,若条件表达式不合法会直接触发编译错误。数据更新延迟问题
若手动修改modelContext
后视图未刷新,需确认是否在主线程执行修改:DispatchQueue.main.async { modelContext.insert(newItem) }
调试查询条件
打印FetchDescriptor
的 SQL 语句(需启用 SwiftData 调试模式):print(descriptor.debugDescription)
五、与 @StateObject
的对比
#
特性 | @Query + FetchDescriptor | @StateObject + 手动查询 |
---|---|---|
数据绑定 | 自动同步 SwiftData 变更 | 需手动调用 fetch 方法 |
适用场景 | 直接依赖 SwiftData 的标准化查询 | 复杂数据聚合或跨模型操作 |
性能 | 内置优化,适合列表渲染 | 需自行实现缓存和批量加载逻辑 |
总结:@Query
与 FetchDescriptor
的结合是 SwiftData 在 SwiftUI 中的标志性设计,通过声明式语法实现高效的数据-UI 同步。开发者应优先使用此模式处理标准化数据查询,仅在需要复杂逻辑时回退到手动管理。