SwiftData — @Query

SwiftData(Apple 最新的数据持久化框架)中,可以通过 Filtering(过滤)和 Sorting(排序)对数据进行查询和组织。这些操作在 SwiftData 的 @Query 属性包装器中得到了优雅且易用的处理。

以下我将通过一些常用的过滤和排序操作演示如何使用 SwiftData 有效地管理数据。


前置理解 #

  1. SwiftData 的核心组件:

    • Model: 数据模型使用 @Model 注解来定义。
    • @Query: 一个强大的属性包装器,用于通过 SwiftData 管理和查询数据。
    • Predicate 和 SortDescriptor: 在过滤和排序中,这些对象用于定义查询条件和排序规则。
  2. 过滤和排序的目标:

    • 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]
  • 说明:
    • 使用 CalendarDate 计算未来 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)
            }
        }
    }
}
  • 使用动态条件
    • 动态更新 @Queryfilter
    • 基于用户输入的 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 简化了常见的 FilteringSorting 操作。
  • 过滤(Filtering) 是通过 #Predicate 定义条件实现,比如部分匹配或日期范围。
  • 排序(Sorting) 是通过 #SortDescriptor 定义规则控制顺序,可支持单一或多重排序。
  • 结合动态过滤和排序,开发者可以实现更灵活的、用户可控的数据查询功能。

通过这些示例,您可以轻松构建复杂的业务逻辑。

本文共 1608 字,上次修改于 Jan 11, 2025