视图重建时如何保持@FetchRequest的nsPredicate与View的@State同步?
问题分析
视图重建时,@FetchRequest的nsPredicate无法像@State那样保留持久化状态,核心原因在于:
@State变量在视图重建时会先临时初始化,随后自动恢复之前的持久化值- 直接修改
items.nsPredicate属于临时变更,当视图因外部参数(如示例中的counter)重建时,@FetchRequest会重新用初始化时的固定predicate创建实例,不会保留之前修改的状态
解决方案
以下两种方案可解决该问题,分别适配不同场景:
方案1:SwiftUI声明式风格——绑定@State与@FetchRequest
将筛选条件存储在@State变量中,让@FetchRequest动态依赖该变量,利用@State的持久化特性保证视图重建时筛选条件不丢失。
修改后的ContentView代码:
struct ContentView: View { @State private var filterPredicate: NSPredicate = NSPredicate(value: false) // 基于@State变量动态创建FetchRequest private var itemsFetchRequest: FetchRequest<Item> { FetchRequest( sortDescriptors: [], predicate: filterPredicate ) } private var items: FetchedResults<Item> { itemsFetchRequest.wrappedValue } var counter: Int init(counter: Int) { print("**** init") self.counter = counter } var body: some View { let _ = Self._printChanges() List { ForEach(items) { item in Text(item.timestamp!, style: .date) } } .onAppear() { print("**** onAppear") let monthYear = Calendar.current.dateComponents([.year, .month], from: Date()) guard let startDate = Calendar.current.date(from: monthYear), let endDate = Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: startDate) else { return } // 更新@State变量,自动触发FetchRequest更新 filterPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ NSPredicate(format: "timestamp >= %@", startDate as NSDate), NSPredicate(format: "timestamp <= %@", endDate as NSDate) ]) } .onReceive(items.publisher.collect()) { _ in print("**** onReceive") print(items) } } }
方案2:自定义控制器——用@StateObject管理NSFetchedResultsController
对于复杂筛选或需要更多控制的场景,通过@StateObject自定义视图模型管理NSFetchedResultsController,利用@StateObject的视图级持久化特性保留筛选状态。
代码示例:
struct ContentView: View { @StateObject private var viewModel = ViewModel() var counter: Int init(counter: Int) { print("**** init") self.counter = counter } var body: some View { let _ = Self._printChanges() List { ForEach(viewModel.items) { item in Text(item.timestamp!, style: .date) } } .onAppear() { print("**** onAppear") viewModel.updatePredicate() } .onReceive(viewModel.$items) { _ in print("**** onReceive") print(viewModel.items) } } } class ViewModel: ObservableObject { @Published var items: [Item] = [] private let fetchedResultsController: NSFetchedResultsController<Item> private let context: NSManagedObjectContext init(context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) { self.context = context let request: NSFetchRequest<Item> = Item.fetchRequest() request.sortDescriptors = [] request.predicate = NSPredicate(value: false) fetchedResultsController = NSFetchedResultsController( fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil ) fetchedResultsController.delegate = self do { try fetchedResultsController.performFetch() items = fetchedResultsController.fetchedObjects ?? [] } catch { print("Fetch error: \(error)") } } func updatePredicate() { let monthYear = Calendar.current.dateComponents([.year, .month], from: Date()) guard let startDate = Calendar.current.date(from: monthYear), let endDate = Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: startDate) else { return } let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ NSPredicate(format: "timestamp >= %@", startDate as NSDate), NSPredicate(format: "timestamp <= %@", endDate as NSDate) ]) fetchedResultsController.fetchRequest.predicate = predicate do { try fetchedResultsController.performFetch() items = fetchedResultsController.fetchedObjects ?? [] } catch { print("Fetch error: \(error)") } } } extension ViewModel: NSFetchedResultsControllerDelegate { func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) { guard let fetchedItems = controller.fetchedObjects as? [Item] else { return } items = fetchedItems } }
验证效果
使用任意方案修改代码后,点击"Recreate View"重建视图,列表会保持原有数据,不会变为空。
内容的提问来源于stack exchange,提问作者John Cashew
相关产品推荐
相关产品推荐

