我被问到的有一个SwiftUI问题比其他任何问题都多:如何动态更改Core Data @FetchRequest
以使用其他谓词或排序顺序?出现问题是因为获取请求被创建为一个属性,因此,如果您尝试使它们引用另一个属性,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"
对于视图的主体,我们将使用带有三个按钮的VStack
,并在注释中添加希望List
显示匹配歌手的位置:
VStack {
// 匹配的歌手列表
Button("添加示例") {
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"
}
}
到目前为止,非常容易。现在,对于有趣的部分:我们需要将// 匹配的歌手列表
替换为真实的东西。这将不会使用@FetchRequest
,因为我们希望能够在初始化程序中创建自定义提取请求,但是我们将使用的代码几乎相同。
创建一个名为“FilteredList”的新SwiftUI视图,并为其提供以下属性:
var fetchRequest: FetchRequest<Singer>
这将存储我们的提取请求,以便我们可以在body
内循环它。但是,由于我们仍然不知道要搜索的内容,因此我们不在此处创建提取请求。相反,我们将创建一个自定义初始化程序,该初始化程序接受过滤器字符串,并使用该字符串来设置fetchRequest
属性。
现在添加此初始化程序:
init(filter: String) {
fetchRequest = FetchRequest<Singer>(
entity: Singer.entity(),
sortDescriptors: [],
predicate: NSPredicate(format: "lastName BEGINSWITH %@", filter)
)
}
这将使用当前的管理对象上下文运行获取请求。由于此视图将在ContentView
中使用,因此我们甚至不需要将托管对象上下文注入到环境中——它将从ContentView
继承上下文。
剩下的就是编写视图主体,这里唯一有趣的是,如果没有@FetchRequest
,我们需要读取fetchRequest
的wrappedValue
属性以提取数据。因此,给出以下内容:
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
的一些实际代码:
FilteredList(filter: lastNameFilter)
现在运行该程序进行尝试:首先点击“添加示例”按钮以创建三个歌手对象,然后点击“Show A”或“ Show S”在姓氏字母之间切换。您应该看到我们的列表根据不同的数据动态更新,具体取决于您按的是哪个按钮。
因此,这项工作需要一点新知识,但实际上并没有那么难——只要您像SwiftUI一样思考,解决方案就在那里。
网友评论