SwiftData框架属性包装器@Query
SwiftData框架属性包装器@Query

SwiftData框架属性包装器@Query

在 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)

        }
    }
}

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注