在 Swift 中,@Query 是 SwiftData 框架(自 Swift 5.9 和 iOS 17 引入)中的一个属性包装器,用于从持久化存储中查询和获取模型数据。它自动从与视图关联的 ModelContext(模型上下文)中获取数据,并持续监听数据的更改,以保持视图的实时更新。
使用 @Query
@Query 主要用于在 SwiftUI 中获取持久化模型的集合。使用 @Query 时,您可以指定查询的条件,SwiftData 会自动处理数据的获取、更新和绑定。
功能与作用
1、自动数据查询:@Query 会根据视图所在的 ModelContext 自动查询指定类型的数据,无需显式编写查询逻辑。例如:
@Query var profiles: [UserProfile]
这行代码会自动查询当前 ModelContext 中所有的 UserProfile 实例,并将它们作为 profiles 数组提供给视图。
2、实时更新视图:如果数据在 ModelContext 中发生增删改等变化,@Query 会自动刷新并触发视图的重绘,因此视图总是反映最新的数据状态。
3、动态过滤和排序(可选):@Query 支持传入条件和排序参数,以控制查询的结果。例如,可以按条件筛选或按某个属性排序:
@Query(filter: #Predicate { $0.age > 18 }, sort: \.name) var profiles: [UserProfile]
上述代码会查询所有 UserProfile 对象中 age 大于 18 的数据,并按 name 属性排序。
使用场景
@Query 非常适合用于需要实时显示或监控数据库中数据集的视图。例如,显示用户列表、任务列表、图书馆书籍清单等都可以使用 @Query 从 ModelContext 中获取数据并确保视图自动更新。
基本语法
@Query 的使用方式如下:
import SwiftUI
import SwiftData
struct ContentView: View {
@Query var people: [Person]
var body: some View {
List(people) { person in
Text("\(person.name), \(person.age) years old")
}
}
}
在这个示例中,@Query 用于获取 Person 类型的数组,people 属性将自动填充持久化存储中符合查询条件的所有 Person 实例。
与 SwiftUI 的集成
@Query 的强大之处在于它与 SwiftUI 的无缝集成。使用 @Query 时,视图会自动观察 people 数组的变化,并在数据变化时自动更新 UI。
示例代码
以下是一个完整的示例,演示如何使用 @Query 获取并显示数据:
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var context
// 查询年龄大于 18 的成年人,并按年龄升序排序
@Query(filter: #Predicate<Person> { $0.age > 18 }, sort: [SortDescriptor(\.age, order: .forward)])
var adults: [Person]
var body: some View {
VStack {
Text("成年人列表")
.font(.headline)
// 列表展示查询结果
List(adults, id: \.self) { person in
Text("\(person.name), 年龄: \(person.age)")
}
}
}
}
#Preview {
let container = try! ModelContainer(for: Person.self)
let context = container.mainContext
let examplePerson = Person(name: "Ali", age: 20)
context.insert(examplePerson)
try? context.save()
return ContentView()
.modelContainer(container)
}
特性和优势
声明式查询:通过 SQL-like 查询语法,可以轻松定义需要获取的数据。
自动更新:@Query 与 SwiftUI 结合,可以在数据变化时自动更新 UI。
类型安全:结合 Swift 的类型系统,确保数据的安全性和一致性。
Query用法
@Query(filter: #Predicate { $0.age > 18 }, sort: \.name)
var adults: [Person]
@Query var adults: [Person]
用法一:带有 filter 和 sort 参数
@Query(filter: #Predicate { $0.age > 18 }, sort: \.name)
var adults: [Person]
这种方式适用于需要筛选或排序查询结果的情况。在这个例子中,@Query 会返回符合条件(年龄大于 18)并按 name 排序的 Person 对象。你可以通过 filter 指定过滤条件,并通过 sort 传入排序规则。
用法二:不带任何过滤或排序条件
@Query var adults: [Person]
这种方式会返回所有的 Person 对象,因为没有添加任何过滤或排序条件。@Query 会自动从 ModelContext 中获取所有 Person 实例,并且不会应用额外的筛选或排序。
何时选择哪种用法
用法一:适合有特定数据筛选、排序需求的场景,直接在声明时指定条件即可,代码更简洁。
用法二:适合获取所有数据或在代码中动态处理数据的情况。
详解@Query用法一代码
@Query(filter: #Predicate { $0.age > 18 }, sort: \.name)
var adults: [Person]
在Query用法一中,借助#Predicate 和 key-path (\.name) 的表达方式来实现数据筛选和排序。这里的语法元素主要包括以下几部分:
@Query 属性包装器
@Query 是 SwiftData 提供的属性包装器,用来自动从数据上下文(ModelContext)中查询数据。
filter 参数
filter 参数接收一个 #Predicate 表达式,用于指定筛选条件。#Predicate 是 Swift 语法中用于定义数据查询的过滤条件,它类似于 SQL 的 WHERE 子句。
@Query(filter: #Predicate { $0.age > 18 }, sort: \.name)
var adults: [Person]
#Predicate { $0.age > 18 }: 这是一个闭包(closure)表达式,$0 表示查询对象的当前实例。$0.age > 18 表示筛选条件,即筛选出 age 大于 18 的 Person 实例。
sort: \.name: sort 接受一个 key-path 表达式 \.name,用于按 name 字段对结果排序。
@Query 属性包装器的常用参数
@Query 属性包装器的常用参数主要有以下几个,适用于 SwiftData 用于筛选、排序以及限制查询结果。下面是 @Query 的主要参数说明:
1、filter
描述: 用于指定查询的过滤条件,相当于 SQL 的 WHERE 子句。
用法: filter: #Predicate { $0.age > 18 } 或使用其他条件逻辑。
示例:
@Query(filter: #Predicate { $0.age > 18 })
var adults: [Person]
只显示名称包含大写 R 的用户:
@Query(filter: #Predicate<User> { user in
user.name.contains("R")
}, sort: \User.name) var users: [User]
匹配名字中带有“R”且居住在伦敦的人:
@Query(filter: #Predicate<User> { user in
user.name.localizedStandardContains("R") &&
user.city == "London"
}, sort: \User.name) var users: [User]
localizedStandardContains方法表示匹配含有R的字符串,它会自动忽略字母的大小写。
支持if和else语句:
@Query(filter: #Predicate<User> { user in
if user.name.localizedStandardContains("R") {
if user.city == "London" {
return true
}
}
return false
}, sort: \User.name) var users: [User]
2、sort
描述: 用于指定查询结果的排序方式,相当于 SQL 的 ORDER BY 子句。
用法: sort: \.propertyName,可以用 key-path 来指定排序字段;可以用 .forward 或 .reverse来指定排序方向。
示例:
@Query(sort: \Book.title) var books: [Book]
@Query(sort: \Book.rating, order: .reverse) var books: [Book]
order 参数指定排序的方向。order 可以接受以下两个选项:
1、.forward:升序排列(从小到大)。
2、.reverse:降序排列(从大到小)。
在代码中,order: .forward 表示按照 rating属性降序排序。
同时可以使用SortDescriptor类型完成:
@Query(sort: [SortDescriptor(\Book.title)]) var books: [Book]
与更简单的排序方法一样,使用排序结果SortDescriptor默认按升序排列,即按文本的字母顺序排列。如果要按 rating 降序排列,可以通过添加 order: .reverse 参数:
@Query(sort: [SortDescriptor(\Book.rating, order: .reverse)]) var books: [Book]
SortDescriptor 是什么?
SortDescriptor 是一种用于排序的描述符,它定义了排序的属性及排序的方向(升序或降序)。在 SwiftData 中,它允许以更复杂的方式构建排序规则,适合于多重排序需求。每个 SortDescriptor 实例可以按一个属性排序,并通过 order 参数指定排序方向。多个 SortDescriptor 可以组合在一起以实现更复杂的排序。
假设要先按 rating 降序排序,然后按 title 升序排序:
@Query(sort: [
SortDescriptor(\Book.rating, order: .reverse),
SortDescriptor(\Book.title)
]) var books: [Book]
这样查询结果将首先按 rating 降序排列,如果有评分相同的书籍,则会按 title 升序排列。
3、animation
描述: 指定数据更新时视图的动画效果。
用法: animation: Animation,可使用标准动画如 .default、.easeIn。
示例:
@Query(animation: .default)
var animatedProfiles: [Profile]
组合示例
多个参数可以组合使用,比如既指定筛选条件又添加排序和限制:
@Query(filter: #Predicate { $0.age > 18 }, sort: \.name, animation: .easeInOut)
var filteredAndSortedAdults: [Person]
以上会筛选出年龄大于 18 的 Person 实例,按 name 升序排序,最多返回 5 条,并带有默认动画效果。
如果想要限制查询结果的数量,可以在 SwiftUI 视图层使用 .prefix() 或 .prefix(_:) 来处理数据。这样可以从 @Query 中获取的完整数据集中选取指定数量的项目。
可以通过在 List 中应用 .prefix(10) 来显示前 10 个符合条件的 Person 数据:
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var context
@Query(filter: #Predicate<Person> { $0.age > 18 }, sort: [SortDescriptor(\.age, order: .forward)], animation: .default)
var adults: [Person]
var body: some View {
VStack {
Text("成年人列表")
.font(.headline)
List(adults.prefix(10), id: \.self) { person in // 限制显示数量
Text("\(person.name), 年龄: \(person.age)")
}
}
}
}
Predicate<Person>显式指定谓语类型
在SwiftUI集成的示例中,存在Predicate<Person>类型。
@Query(filter: #Predicate<Person> { $0.age > 18 }, sort: [SortDescriptor(\.age, order: .forward)])
在 SwiftData 中,给 #Predicate<Person> 添加泛型类型 <Person> 是用于显式地指定谓词的类型。在某些情况下,Swift 可以通过上下文推断出类型,但如果编译器无法自动推断,就需要显式指定类型。
什么时候可以省略 <Person>
在一些简单查询中,比如直接在 @Query 的上下文内定义谓词,Swift 通常可以推断类型,因此可以省略 <Person>:
@Query(filter: #Predicate { $0.age > 18 })
var adults: [Person]
在这个例子中,Swift 能推断出谓词应用于 Person 类型的查询,因此可以省略 <Person>。
什么时候需要显式指定 <Person>
如果 Swift 编译器无法推断类型(例如,查询条件变复杂或出现了其他类型的冲突),可能会要求手动添加类型信息以帮助编译器正确识别。在这种情况下,添加 <Person> 明确指定 #Predicate 作用的模型类型,可以解决编译错误:
@Query(filter: #Predicate<Person> { $0.age > 18 })
var adults: [Person]
因此,虽然不是总是必须的,但在编译器提示无法推断类型的情况下,明确指定类型是一种安全的选择。
总结
@Query 是 SwiftData 框架中的一个重要属性包装器,用于从持久化存储中声明式地查询数据。
它允许开发者以简单直观的方式获取和展示数据,同时与 SwiftUI 的状态管理系统完美结合,自动处理数据变化导致的 UI 更新。
通过使用 @Query,可以更轻松地构建数据驱动的应用程序,减少样板代码。
完整代码
ContentView代码
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var context
// 查询年龄大于 18 的成年人,并按年龄升序排序
@Query(filter: #Predicate<Person> { $0.age > 18 }, sort: [SortDescriptor(\.age, order: .forward)])
var adults: [Person]
var body: some View {
VStack {
Text("成年人列表")
.font(.headline)
// 列表展示查询结果
List(adults, id: \.self) { person in
Text("\(person.name), 年龄: \(person.age)")
}
}
}
}
#Preview {
let container = try! ModelContainer(for: Person.self)
let context = container.mainContext
let examplePerson = Person(name: "Ali", age: 20)
context.insert(examplePerson)
try? context.save()
return ContentView()
.modelContainer(container)
}
Person代码
import Foundation
import SwiftData
@Model
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
入口文件代码
import SwiftUI
import SwiftData
@main
struct hackingwithswiftApp: App {
@State private var container: ModelContainer
init() {
container = try! ModelContainer(for: Person.self)
}
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(container)
}
}
}