ios - 当更改源为共享扩展时,@FetchRequest 结果未在 SwiftUI 中更新

标签 ios swift core-data swiftui

@FetchRequest 上有很多问题,其中的答案提供了强制 SwiftUI 重绘 View 的变通方法。我找不到任何解决这个问题的方法。

我的应用程序使用共享扩展将新项目插入我的 CoreData模型(sqlite)。在主应用程序和共享扩展目标之间共享一个框架。 SwiftUI 主 ContentView在我的应用程序中使用 @FetchRequest属性来监视数据库的内容并更新 View :

struct ContentView: View {
    @FetchRequest(fetchRequest: MyObj.allFetchRequest()) var allObjs: FetchedResults<MyObj>
    ...
}

我的 xcdatamodel 扩展为:

    public static func allFetchRequest() -> NSFetchRequest<MyObj> {
        let request: NSFetchRequest<MyObj> = MyObj.fetchRequest()
        // update request to sort by name
        request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        return request
    }

共享扩展使用以下命令添加新项目:

let newObj = MyObj(context: context)
newObj.name = "new name"
try! context.save()

使用共享扩展添加新项目后,当我通过在设备上切换应用程序返回我的应用程序时,SceneDelegate检测到 sceneDidBecomeActive .在这个函数中,我可以手动运行一个 fetch 请求并查看新项目是否已添加。我什至可以使用其他 SO 问题的答案来欺骗 UI 在 ContentView 中重建(通过在收到通知时切换构建器中使用的 Bool),但无论哪种情况,当 UI 重建时,allObjs不包含来自数据库的更新结果。

在我的 sceneDidBecomeActive我已经尝试了多种代码组合,以尝试以 @FetchRequest 的方式使用数据库的最新内容更新上下文。会看到的。数据在那里,我的托管上下文可以看到这个新数据,因为手动获取请求返回新项目,但是 @FetchRequest总是过时阻止 UI 重建。在这两个位置,我都打印了上下文,并且确信它是相同的上下文。我试过了:
  • context.reset()
  • context.refreshAllObjects()
  • context.save()

  • 我不知道为什么 @FetchRequest ContentView 时不更新其值重绘。

    如何强制 @FetchRequest在我的共享扩展将新对象添加到数据库后更新其对象并导致 SwiftUI 重建?

    (Xcode 11.4.1,Swift 5,目标:iOS 13.1)

    编辑:

    在我的 ContentView ,我打印 allObjs变量、它们的状态、上下文的内存地址等。我将其与从上下文中获取的新值进行比较,现有对象的所有值都是相同的,除了新的获取显示新添加的对象,而 allObjs不包含新对象。很明显 @FetchRequest对象未更新。

    编辑 2:

    我尝试创建自定义 ObservableObject带有 @Published 的类属性和显式 update()方法,然后将其用作 @ObservedObjectContentView .这行得通。

    class MyObjList: ObservableObject {
        static let shared = MyObjList()
        @Published var allObjs: [MyObj]!
    
        init() {
            update()
        }
    
        func update() {
            allObjs = try! DataStore.persistentContainer.viewContext.fetch(MyObj.allFetchRequest())
        }
    }
    

    然后在 ContentView :

    @EnvironmentObject var allObjs: MyObjList
    

    这有助于确认数据存在,它只是没有被 @FetchRequest 正确更新.这是一个功能性的解决方法。

    最佳答案

    我使用持久历史跟踪和远程更改通知解决了类似的问题。这些使 Core Data 记录 SQLite 存储发生的每一个更改,并在这些更改之一远程发生时(例如,从扩展程序或 iCloud)向您发送通知。
    默认情况下不启用这些功能。您必须在加载持久存储之前配置它们:

    persistentStoreDescriptions.forEach {
        $0.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        $0.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
    }
    
    然后订阅远程更改通知:
    NotificationCenter.default.addObserver(
        /* The persistent container/view model/MyObjList/etc that acts as observer */, 
        selector: #selector(coordinatorReceivedRemoteChanges(_:)), 
        name: .NSPersistentStoreRemoteChange, 
        object: persistentStoreCoordinator // don't forget to pass the PSC as the object!
    )
    
    现在我们会在发生远程更改时收到通知,但我们需要在整个应用程序中与托管对象上下文共享这些更改。这对于每个应用程序来说看起来都不同:有时您的 MOC 根本不需要知道这些变化,有时他们只需要了解其中的一些,有时是全部。苹果最近published a guide深入探讨了如何确保只处理相关的更改。
    在这种情况下,您可能会这样做:
    @objc
    func coordinatorReceivedRemoteChanges(_ notification: Notification) {
        let currentToken = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken
        let existingToken = ... // retrieve the previously processed history token so we don't repeat work; you might have saved it to disk using NSKeyedArchiver
        
        // Fetch the history
        let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: existingToken)
        let result = try! context.execute(historyRequest) as! NSPersistentHistoryResult
        let transactions = result.result as! [NSPersistentHistoryTransaction]
    
        // Filter out the non-relevant changes
        for transaction in transaction {
            // Maybe we only care about changes to MyObjs 
            guard transaction.entityDescription.name == MyObj.entity().name else { continue }
            // Maybe we only care about specific kinds of changes
            guard let changes = transaction.changes, changes.contains(where: { ... }) else { continue }
            // Merge transaction into the view context
            viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
        }
    
        // Finally, save `currentToken` so it can be used next time and optionally delete old history.
    }
    
    任意 @FetchRequest使用 viewContext现在将接收所有合并的更改并相应地刷新其数据。

    关于ios - 当更改源为共享扩展时,@FetchRequest 结果未在 SwiftUI 中更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61679939/

    相关文章:

    ios - 如何为 iPhone 5 更新完全由图像组成的应用程序

    ios - 在 iOS 设备中创建 Bonjour AirPrint 服务

    ios - 如何为 Mac OS X 创建苹果邮件插件

    swift - 使用 swift 发布二进制数据

    ios - 更改 UINavigationController 颜色和字体

    ios - 核心数据在后台保存对象问题

    core-data - 应用在 executeFetchRequest 上崩溃

    ios - 更新 Int32 类型的 CoreData 属性

    swift - GameplayKit 找到移动 Sprite 的路径

    ios - NSPredicate 在 NSSet 中查找具有属性的对象