译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
在 SwiftUI 中动态过滤 @FetchRequest
对于 SwiftUI ,我经常被问到的一个问题是:我要怎么样动态地改变一个 Core Data@FetchRequest
,以便使用不同的谓词或者排序呢?大家之所以会提出这个问题是因为 fetch 请求是作为属性被创建的,因此如果你尝试让它们引用另外的属性,会被 Swift 拒绝。
这里有一个简单的解决方案,而且你回想的话会发现这其实是非常明显的方案:因为几乎所有其他东西也是这么运作的:我们应该把这个功能分割到一个独立的视图,然后把值注入进去。
我想用一些实际的代码来说明,所以我在下面放了一些最简单的例子:添加三个歌手到 Core Data,然后用两个按钮分别显示姓以 A 或者 S 结尾的歌手。
创建一个叫 Singer 的 Core Data 实体,给它两个字符串书写:“firstName” 和 “lastName”。用数据模型检视器把它的 Codegen 改成 Manual/None,然后进入 Editor 菜单,选择 Create NSManagedObject Subclass 以便我们得到一个可以自定义的Singer
类。
等 Xcode 为我们生成好文件,打开 Singer+CoreDataProperties.swift,然后添加下面两个属性,让这个类能更好地配合 SwiftUI 使用:
var wrappedFirstName: String {
firstName ?? "Unknown"
}
var wrappedLastName: String {
lastName ?? "Unknown"
}
接下来是实际的工作。
接下来是设计一个托管我们的信息的视图。像我之前说过的,它会有两个按钮,改变视图信息的过滤器,还有一个用于插入测试数据的按钮。
首先,添加两个属性到 ContentView
结构体,以便我们有能够保持对象的托管对象上下文,以及一个作为过滤器使用的状态:
@Environment(\.managedObjectContext) var moc
@State private var lastNameFilter = "A"
对于视图的body
,我们会用一个VStack
包裹三个按钮,再加上一个放匹配的歌手列表的注释占位:
VStack {
// list of matching singers
Button("Add Examples") {
let taylor = Singer(context: self.moc)
taylor.firstName = "Taylor"
taylor.lastName = "Swift"
let ed = Singer(context: self.moc)
ed.firstName = "Ed"
ed.lastName = "Sheeran"
let adele = Singer(context: self.moc)
adele.firstName = "Adele"
adele.lastName = "Adkins"
try? self.moc.save()
}
Button("Show A") {
self.lastNameFilter = "A"
}
Button("Show S") {
self.lastNameFilter = "S"
}
}
目前为止,一切都很简单,接下来是有趣的部分:我们需要替换掉// list of matching singers
这个注释,用实际的实现代替。这里不会用到@FetchRequest
注解,因为我们要在构造器里创建一个自定义 fetch 请求,但代码几乎是一样的。
创建一个叫 “FilteredList” 的 SwiftUI 视图,给它这个属性:
var fetchRequest: FetchRequest<Singer>
这个属性用来存储我们的 fetch 请求,以便我们可以在body
里遍历它。不过,我们并不马上创建它,因为我们还不知道我们要找的是什么东西。相反,我们要创建一个自定义构造器,接收一个过滤字符串,然后用这个字符串来设置fetchRequest
属性。
添加下面这个构造器:
init(filter: String) {
fetchRequest = FetchRequest<Singer>(entity: Singer.entity(), sortDescriptors: [], predicate: NSPredicate(format: "lastName BEGINSWITH %@", filter))
}
这会得到一个用当前的托管对象上下文构建的 fetch 请求。因为这个视图会被用在ContentView
内部,所以我们不必为其注入托管对象上下文到环境中 —— 它会继承来自ContentView
的上下文。
剩下的事情就是完成视图的body
,这里唯一有趣的事情是,没有了@FetchRequest
,我们需要访问fetchRequest
的wrappedValue
属性来拉出我们的数据。因此,视图的body
实现如下:
var body: some View {
List(fetchRequest.wrappedValue, id: \.self) { singer in
Text("\(singer.wrappedFirstName) \(singer.wrappedLastName)")
}
}
如果你不喜欢使用fetchRequest.wrappedValue
,可以创建一个简单的计算属性:
var singers: FetchedResults<Singer> { fetchRequest.wrappedValue }
至于FilteredList
的预览,你可以直接移除。
这样一来视图就完成了,我们可以回到ContentView
,把注释用实际的代码实现,如下:
FilteredList(filter: lastNameFilter)
运行应用尝试下:点击 Add Examples 按钮先创建三个歌手,然后点击 “Show A” 或者 “Show S” 来触发不同的姓氏过滤。你应该会看到列表随着你点击不同的按钮,动态更新不同的数据。
为了让这一切工作,用到一点新的知识,但算不上难 —— 只要你以 SwiftUI 的方式思考,答案会跃然纸上。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~