SwiftData过滤条件#Predicate
SwiftData过滤条件#Predicate

SwiftData过滤条件#Predicate

在 SwiftData 中,Predicate 是一个强类型、声明式的过滤条件工具,用于从数据模型中筛选符合条件的数据。它提供了更安全、更直观的查询方式,与 Swift 的类型系统紧密结合。

核心概念

1、定义方式

使用 #Predicate 表示,这是一种编译时检查的过滤条件构造器,确保在运行时之前验证语法和类型正确性。

2、与 SwiftData 集成

它通常与 SwiftData 的查询机制(如 @Query)一起使用,用来筛选持久化存储中的数据。

3、类型限制

Predicate 是泛型类型,指定用于某种数据模型(如 Predicate<Place>)。

使用 Predicate 的示例

1、简单筛选条件

let predicate = #Predicate<Place> {
    $0.name.localizedStandardContains("park")
}

上例筛选出 name 属性中包含 “park” 的 Place 对象。

2、多条件筛选

let predicate = #Predicate<Place> {
    $0.name.localizedStandardContains("park") && $0.interested
}

同时满足以下条件的数据会被选中:

1、name 包含 “park”。

2、interested 属性为 true。

3、动态筛选条件

func createPredicate(searchText: String, filterInterested: Bool) -> Predicate<Place> {
    #Predicate<Place> {
        if !searchText.isEmpty && filterInterested {
            $0.name.localizedStandardContains(searchText) && $0.interested
        } else if !searchText.isEmpty {
            $0.name.localizedStandardContains(searchText)
        } else if filterInterested {
            $0.interested
        } else {
            true
        }
    }
}

根据 searchText 和 filterInterested 动态生成不同的筛选条件。

Predicate 的构造方式

1、固定属性引用

使用 $0 表示当前查询的对象实例。它的属性需要是模型定义中的持久化属性。

2、逻辑操作

&&:表示逻辑 “与”。

||:表示逻辑 “或”。

!:表示逻辑 “非”。

3、比较运算

支持标准比较运算符(如 ==, !=, <, > 等)。

示例:$0.age > 30 筛选年龄大于 30 的对象。

4、字符串操作

localizedStandardContains: 检查子字符串的存在(区分大小写)。

hasPrefix, hasSuffix: 检查字符串前缀或后缀。

示例:$0.name.hasPrefix(“John”) 筛选名字以 “John” 开头的数据。

5、动态逻辑

Predicate 支持 if 条件和变量插值,但不支持在表达式中直接调用复杂方法。

与 @Query 结合使用

在 SwiftUI 中,Predicate 通常与 @Query 属性包装器结合,用来查询持久化存储的数据:

@Query(filter: #Predicate<Place> { $0.name.starts("be") })
private var places: [Place]

动态更新筛选条件:

@State private var searchText = ""

@Query var places: [Place]

init() {
    _places = Query(filter: #Predicate<Place> {
        $0.name.localizedStandardContains(searchText)
    })
}

与搜索功能结合使用

在SwiftUI视图中实现搜索功能时,也可以通过#Predicate进行筛选。

import SwiftUI
import SwiftData

struct PlaceList: View {
    @Query(sort:\Place.name) private var places: [Place]

    @State private var showImages = false
    @State private var searchText = ""
    @State private var filterByInterested = false
    
    var body: some View {
        NavigationStack {
            List(places) { place in
                HStack {
                    place.image
                        .resizable()
                        .scaledToFit()
                        .clipShape(.rect(cornerRadius: 7))
                        .frame(width: 100, height: 100)
                    Text(place.name)
                    Spacer()
                    if place.interested {
                        Image(systemName: "star.fill")
                            .foregroundColor(Color.yellow)
                            .padding(.trailing)
                    }
                }
            }
            .navigationTitle("Places")
            .searchable(text: $searchText ,prompt: "Find a Place")
            .animation(.default, value:searchText)
            .toolbar {
                ToolbarItem(placement:.topBarLeading) {
                    Button("Filter", systemImage: filterByInterested ? "star.fill" : "star") {
                        withAnimation {
                            filterByInterested.toggle()
                        }
                    }
                    .tint(filterByInterested ? .yellow : .blue)
                }
                ToolbarItem(placement:.topBarTrailing) {
                    Button("Show Image", systemImage: "photo") {
                        showImages.toggle()
                    }
                }
            }
            .sheet(isPresented: $showImages, content: {
                Scrolling()
            })
        }
    }
}

在这段代码中,基本的功能为一个List列表。通过searchable实现一个信息检索的功能。这里的searchable就可以通过#Predicate实现。

首先声明一个#Predicate<Place>类型的变量:

private var predicate: Predicate<Place> {
    #Predicate<Place> {
        if !searchText.isEmpty && filterByInterested == true {
            $0.name.localizedStandardContains(searchText) && $0.interested
        } else if !searchText.isEmpty {
            $0.name.localizedStandardContains(searchText)
        } else if filterByInterested == true {
            $0.interested
        } else {
            true
        }
    }
}

代码解析

1、计算属性的意义

private var predicate: Predicate<Place> { ... }

动态生成: 每次访问 predicate 时,都会根据当前的 searchText 和 filterByInterested 的值重新生成筛选条件。

简洁复用:通过集中定义 predicate,可以避免重复代码,将复杂的筛选逻辑封装成一个易维护的单元。

2、筛选逻辑

#Predicate<Place> { ... }

#Predicate 是 SwiftData 提供的一种类型安全的查询工具,可以用类似 Swift 的逻辑语法构建复杂的筛选条件。

作用:定义模型 Place 的筛选逻辑。

类型约束:这里的泛型 <Place> 表明此谓词仅适用于 Place 数据模型。

编译时检查:#Predicate 会在编译时验证筛选条件的语法和类型是否正确。

调用#Predicate

在代码中,通过places.filter(predicate)表明希望动态过滤本地数据集(places),并不是直接通过 @Query 从 SwiftData 数据存储中查询。

List((try? places.filter(predicate)) ?? places) { place in ...}

1、本地数据过滤

places 是一个已加载的数据数组,filter(predicate) 是对这个数组执行的操作。

它使用的是 Swift 的标准数组方法 filter,而不是 SwiftData 内置的查询功能。

2、try? 包裹

如果 filter 方法抛出异常(如 predicate 无效),它会返回原始数据 places,避免崩溃。

3、差异原因

这里的 predicate 是从计算属性中生成的,而不是直接绑定到 @Query 的筛选条件中。

实现效果

对比searchable修饰符文章中的筛选条件,使用#Predicate可能看起来比较复杂,但因为#Predicate同样支持条件筛选,因此单独拿出来讲一下。

优势

1、类型安全

Predicate 在编译时检查语法和类型,避免运行时错误。

2、简洁声明式

使用 Swift 的语法直接表达筛选条件,避免复杂的字符串拼接查询。

3、优化性能

查询条件被直接传递到底层存储引擎执行,减少数据加载和过滤的开销。

注意事项

1、受限的动态性

Predicate 是编译时检查的,因此不支持所有动态计算逻辑(如动态函数调用)。

2、性能

确保筛选逻辑简洁高效,因为复杂的筛选条件可能影响查询性能。

3、SwiftData 版本要求

确保使用支持 #Predicate 的 SwiftData 和 Swift 版本。

Predicate 是 SwiftData 的一个强大特性,结合类型安全和声明式编程理念,适合在数据驱动的 SwiftUI 应用中高效实现筛选和查询功能。

参考文章

1、SwiftData框架属性包装器@Query:https://fangjunyu.com/2024/11/04/swiftdata%e6%a1%86%e6%9e%b6%e5%b1%9e%e6%80%a7%e5%8c%85%e8%a3%85%e5%99%a8query/

2、iOS 18, SwiftUI 6, & Swift 6: 从零开始构建iOS应用程序, 涵盖visionOS, macOS, watchOS:https://www.bilibili.com/video/BV1b6421f7Px?spm_id_from=333.788.videopod.episodes&vd_source=f21219cb93118beac6a36b0ef961df6a&p=8

3、SwiftUI视图内实现搜索功能的searchable修饰符:https://fangjunyu.com/2024/12/23/swiftui%e8%a7%86%e5%9b%be%e5%86%85%e5%ae%9e%e7%8e%b0%e6%90%9c%e7%b4%a2%e5%8a%9f%e8%83%bd%e7%9a%84searchable%e4%bf%ae%e9%a5%b0%e7%ac%a6/

扩展部分

与搜素功能结合的完整代码

import SwiftUI
import SwiftData

struct PlaceList: View {
    @Query(sort:\Place.name) private var places: [Place]

    @State private var showImages = false
    @State private var searchText = ""
    @State private var filterByInterested = false
    
    private var predicate: Predicate<Place> {
        #Predicate<Place> {
            if !searchText.isEmpty && filterByInterested == true {
                $0.name.localizedStandardContains(searchText) && $0.interested
            } else if !searchText.isEmpty {
                $0.name.localizedStandardContains(searchText)
            } else if filterByInterested == true {
                $0.interested
            } else {
                true
            }
        }
    }
    var body: some View {
        NavigationStack {
            List((try? places.filter(predicate)) ?? places) { place in
                HStack {
                    place.image
                        .resizable()
                        .scaledToFit()
                        .clipShape(.rect(cornerRadius: 7))
                        .frame(width: 100, height: 100)
                    Text(place.name)
                    Spacer()
                    if place.interested {
                        Image(systemName: "star.fill")
                            .foregroundColor(Color.yellow)
                            .padding(.trailing)
                    }
                }
            }
            .navigationTitle("Places")
            .searchable(text: $searchText ,prompt: "Find a Place")
            .animation(.default, value:searchText)
            .toolbar {
                ToolbarItem(placement:.topBarLeading) {
                    Button("Filter", systemImage: filterByInterested ? "star.fill" : "star") {
                        withAnimation {
                            filterByInterested.toggle()
                        }
                    }
                    .tint(filterByInterested ? .yellow : .blue)
                }
                ToolbarItem(placement:.topBarTrailing) {
                    Button("Show Image", systemImage: "photo") {
                        showImages.toggle()
                    }
                }
            }
            .sheet(isPresented: $showImages, content: {
                Scrolling()
            })
        }
    }
}

#Preview {
    PlaceList()
        .modelContainer(Place.preview)
}

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

发表回复

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