SwiftUI CoreData FetchRequest 重新设计
SwiftUI CoreData @FetchRequest 重新设计
SwiftUI 的 @FetchRequest
有一个不佳之处:如果包含 @FetchRequest
的视图重新初始化,它的排序描述符将丢失。这种重新设计尝试通过将它重新实现为 View
而不是属性包装器的方式来解决此问题,类似于其他纯数据 View
,例如 ForEach
。这样允许在父 View
中将排序描述符作为 @State
真相源,并将其传递给获取请求,以防止它们丢失。
此代码库包含一个示例项目,该项目并排显示了原始获取请求和重新设计,并演示了该缺陷以及如何防止该缺陷。只需在 macOS 或 iPad 横屏上启动项目(因此出现表排序头),通过单击头修改两个表的排序,然后单击计数器增量按钮以导致两个视图重新初始化。
重新设计包括一个 FetchMonitor
View
,它包含一个返回与 AsyncImage
相似的 phase
的闭包。它包含更新的获取结果或错误。在内部,它有一个充当 NSFetchedResultsController
代理的 @StateObject
。FetchMonitor
的用法如下:
struct FetchViewRedesign: View {
@State private var sortDescriptors = [SortDescriptor(\Item.timestamp, order: .forward)]
var body: some View {
FetchMonitor(sortDescriptors: sortDescriptors) { phase in
if let items = phase.results {
Table(items, sortOrder: $sortDescriptors) {
TableColumn("timestamp", value: \.timestamp) { item in
Text(item.timestamp!, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
}
else if let error = phase.error {
Text(error.localizedDescription)
}
else {
Text("Empty")
}
}
}
}
正如你所看到的,sortDescriptors
是一个 @State
,当这个 FetchViewRedesign
重新初始化时不会丢失。它们被传递到监视器中,并在用户单击表头时向 Table
提供绑定。
如果你希望你的排序依据是一个简单的布尔值而不是 SortDescriptor
怎么办?可以使用几个计算属性来完成,如下所示:
@State var ascending = false
var sortDescriptors: [SortDescriptor<Item>] {
[SortDescriptor(\Item.timestamp, order: ascending ? .forward : .reverse)]
}
var sortOrder: Binding<[SortDescriptor<Item>]> {
Binding {
sortDescriptors
} set: { value in
ascending = value.first?.order == .forward
}
}
...
Button("Toggle sort") {
ascending.toggle()
}
...
FetchMonitor(sortDescriptors: sortDescriptors) { phase in
if let results = phase.results {
Table(results, sortOrder: sortOrder) {
``
现在可以轻松地将布尔值更改为 `@SceneStorage` 或 `@AppStorage`,如果你希望它持久化的话。