[SwiftUI 100天] 使用 NSPredicate 过滤 @FetchRequest

1,018 阅读3分钟
译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀

使用 NSPredicate 过滤 @FetchRequest

在使用 SwiftUI 的@FetchRequest属性包装器时,我们可以提供一个排序描述符的数组,用于控制结果的顺序,同时我们还可以提供一个NSPredicate来控制哪些结果应该被显示。Predicate,即谓词,是简单的测试,这个测试会被应用到我们的 Core Data 实体中的每个对象 —— 只有通过测试的对象才会被放进结果数组。

NSPredicate 的语法并不算好猜,但实际上你也只需要少数几种 predicate,因此情况不算太糟。

为了体验谓词,让我们创建一个叫 Ship 的实体,包含两个字符串书写:“name” 和 “universe”。

然后把 ContentView.swift 改成下面这样:

import CoreData
import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: nil) var ships: FetchedResults<Ship>

    var body: some View {
        VStack {
            List(ships, id: \.self) { ship in
                Text(ship.name ?? "Unknown name")
            }

            Button("Add Examples") {
                let ship1 = Ship(context: self.moc)
                ship1.name = "Enterprise"
                ship1.universe = "Star Trek"

                let ship2 = Ship(context: self.moc)
                ship2.name = "Defiant"
                ship2.universe = "Star Trek"

                let ship3 = Ship(context: self.moc)
                ship3.name = "Millennium Falcon"
                ship3.universe = "Star Wars"

                let ship4 = Ship(context: self.moc)
                ship4.name = "Executor"
                ship4.universe = "Star Wars"

                try? self.moc.save()
            }
        }
    }
}

然后点击按钮注入一些范例数据到 Core Data,不过目前我们还没有应用谓词。为了解决这个问题,我们要把谓词参数的nil值替换成某种可以被应用给我们的对象的测试。

举个例子,我们可以要求飞船必须来自 Star Wars,像这样:

@FetchRequest(entity: Ship.entity(), sortDescriptors: [], predicate: NSPredicate(format: "universe == 'Star Wars'")) var ships: FetchedResults<Ship>

如果你的数据包含引号,那么像上面的写法就会带来麻烦。所以通常我们会用特殊语法代替:%@表示“在这里插入某些数据”,可以让我们以参数而不是内联的方式书写谓词。

所以,把 predicate 写法改成这样:

NSPredicate(format: "universe == %@", "Star Wars"))

不仅可以用==,我们还可以用诸如<>这样的比较来过滤对象。例如,下面的例子会返回 Defiant, Enterprise 和 Executor:

NSPredicate(format: "name < %@", "F")) var ships: FetchedResults<Ship>

%@在幕后做了大量的工作,特别是把原生 Swift 类型转换成它们对应的 Core Data 类型。例如,我们可以用一个IN谓词来检查某个宇宙是否来自某个数组的三个选项之一,像这样:

NSPredicate(format: "universe IN %@", ["Aliens", "Firefly", "Star Trek"])

我们还可以用一个谓词来检查一个字符串的部分,用诸如BEGINSWITHCONTAINS这样的操作符。例如,下面的代码会返回所有以大写字母 E 开头的飞船:

NSPredicate(format: "name BEGINSWITH %@", "E"))

谓词是大小写敏感的,如果你想忽略大小写,需要把代码改成下面这样:

NSPredicate(format: "name BEGINSWITH[c] %@", "e"))

CONTAINS[c] 的工作方式类似,差异就在于你的子字符串可以出现在属性值的任何部分,而不仅是头部。

最后,你还可以用 NOT 来反转谓词,得到常规行为相反的结果。例如,下面的代码可以找出所有不以 E 开头的飞船:

NSPredicate(format: "NOT name BEGINSWITH[c] %@", "e"))

如果你想要更复杂的谓词,可以用AND尽可能精确地组合它们,或者导入 Core Data,尝试一下NSCompoundPredicate—— 它能让你基于几个更小的谓词构建出一个大的谓词。


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~