SwiftUI CoreData FetchRequest 重新设计

SwiftUI CoreData @FetchRequest 重新设计

SwiftUI 的 @FetchRequest 有一个不佳之处:如果包含 @FetchRequest 的视图重新初始化,它的排序描述符将丢失。这种重新设计尝试通过将它重新实现为 View 而不是属性包装器的方式来解决此问题,类似于其他纯数据 View,例如 ForEach。这样允许在父 View 中将排序描述符作为 @State 真相源,并将其传递给获取请求,以防止它们丢失。

此代码库包含一个示例项目,该项目并排显示了原始获取请求和重新设计,并演示了该缺陷以及如何防止该缺陷。只需在 macOS 或 iPad 横屏上启动项目(因此出现表排序头),通过单击头修改两个表的排序,然后单击计数器增量按钮以导致两个视图重新初始化。

重新设计包括一个 FetchMonitor View,它包含一个返回与 AsyncImage 相似的 phase 的闭包。它包含更新的获取结果或错误。在内部,它有一个充当 NSFetchedResultsController 代理的 @StateObjectFetchMonitor 的用法如下:

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 提供绑定。

Screenshot

如果你希望你的排序依据是一个简单的布尔值而不是 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`,如果你希望它持久化的话。

GitHub

点击跳转