You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

视图重建时如何保持@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 15:33:13