带有核心数据的 iOS Today 扩展

Posted

技术标签:

【中文标题】带有核心数据的 iOS Today 扩展【英文标题】:iOS today extension with core data 【发布时间】:2020-03-01 14:15:52 【问题描述】:

我正在尝试为我的 ios 应用程序进行今天的扩展。 今天的扩展将根据与核心数据一起保存的数据显示“下一个”课程。 我一直在做一些研究,我知道我必须与 appGroup 共享我的 persistentContainer。 所以我做到了:

为 ios 应用和今天的扩展目标启用了 appGroups。 编写了一个函数来分享它:
public extension NSPersistentContainer 
    func addToAppGroup(id: String) 
        guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: id) else 
            fatalError("Shared file container could not be created.")
        
        let storeURL = fileContainer.appendingPathComponent("\(self.name).sqlite")
        let storeDescription = NSPersistentStoreDescription(url: storeURL)
        self.persistentStoreDescriptions.append(storeDescription)
    

然后在我的核心数据堆栈中:

internal class CoreDataContainer 

    static var persistentContainer: NSPersistentContainer = 
        let container = NSPersistentCloudKitContainer(name: "SchoolCompanion")
        container.addToAppGroup(id: "group.com.Ce-dricLoneux.School-Companion")
        container.loadPersistentStores(completionHandler:  (_, error) in
            if let error = error as NSError? 
                fatalError("Unresolved error \(error), \(error.userInfo)")
            
        )
        return container
    ()

    // MARK: - Core Data Saving support

    func saveContext () 
        let context = CoreDataContainer.persistentContainer.viewContext
        if context.hasChanges 
            do 
                try context.save()
             catch 
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            
        
    

文件和 xcdatamodel 在两个目标之间共享。 在这一点上,我认为我可以从我的扩展中访问我的核心数据,但是当我执行获取请求时,我没有得到任何结果。 controllerDidChangeContent 永远不会被执行。

class TodayViewController: UIViewController, NCWidgetProviding 
private func configureFetchedResultsController() 
        let request: NSFetchRequest<Course> = Course.fetchRequest()
        request.sortDescriptors =  []
        self.fetchedResultsController = NSFetchedResultsController<Course>(fetchRequest: request, managedObjectContext: CoreDataContainer.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
        self.fetchedResultsController.delegate = self
        do 
            try self.fetchedResultsController.performFetch()
         catch 
            print(error.localizedDescription)
        
    

override func viewDidLoad() 
        super.viewDidLoad()

        self.extensionContext?.widgetLargestAvailableDisplayMode = .compact
        self.configureFetchedResultsController()

    

func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) 
        // Perform any setup necessary in order to update the view.

        // If an error is encountered, use NCUpdateResult.Failed
        // If there's no update required, use NCUpdateResult.NoData
        // If there's an update, use NCUpdateResult.NewData

        completionHandler(NCUpdateResult.newData)
    


// MARK: - FetchedResultsController Delegate

extension TodayViewController: NSFetchedResultsControllerDelegate 
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) 
        print("content did change")
    

我也尝试直接进行获取请求,但结果是一个空数组。 为什么我没有得到任何结果?

另一件事:每次从 ios 应用程序更新数据时,我使用 nsfetchedResult 控制器刷新视图数据,这样可以吗,还是应该使用 widgetPerformUpdate 方法?在那种情况下,我们不知道今天的扩展什么时候会被刷新,它可能会显示过时的数据。

使用的主要文档:https://www.avanderlee.com/swift/core-data-app-extension-data-sharing/

【问题讨论】:

您是否能够弄清楚如何将您的数据连接到您今天的扩展程序?我目前遇到了同样的问题 @fphhelp 你在使用 cloudKit 容器吗? 是的,我在用户打开 iCloud 时使用 CloudKit 容器,在关闭时使用常规 NSPersistentContainer 两者都可以使用云容器,尝试添加 yourStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "yourContainerID") 【参考方案1】:

您应该像下面这样子类化 NSPersistentCloudKitContainer,返回 defaultDirectoryURL() 的 App Group URL。然后在您的 CoreDataStack 中,使用 let container = GroupedPersistentCloudKitContainer(name: "SchoolCompanion")。同时删除您对addToAppGroup(...) 的呼叫。您需要在 App 和 Extension 中实例化 GroupedPersistentCloudKitContainer,您还需要确保 GroupedPersistentCloudKitContainer 链接到两个 Target。

class GroupedPersistentCloudKitContainer: NSPersistentCloudKitContainer 

    enum URLStrings: String 
        case group = "group.com.yourCompany.yourApp"
    


    override class func defaultDirectoryURL() -> URL 
        let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: URLStrings.group.rawValue)

        if !FileManager.default.fileExists(atPath: url!.path) 
            try? FileManager.default.createDirectory(at: url!, withIntermediateDirectories: true, attributes: nil)
        
        return url!
    
    ...

【讨论】:

我在defaultDirectoryURL() 函数行上收到此错误:Overriding non-@objc declarations from extensions is not supported。我完全使用了你写的东西,所以我不确定为什么会这样 @fphelp 我没有看到那个错误,但听起来你在扩展中声明了这个类。在您的应用程序中声明它,并通过将目标设置为两者来确保它在您的扩展程序中可访问。 我从小部件中休息了一下,因为它太难了,但现在我又回到了它,不得不重新编码所有东西。我刚刚将 GroupedPersistentCloudKitContainer 添加到我的代码中,但是当我尝试运行该应用程序时,我在 if !FileManager.default.fileExists(atPath: url!.path) 行收到一个错误,说它在展开 url 时不会出错。我相信这意味着网址为零。我进行了三次检查,我为URLStrings.group.rawValue 使用了正确的字符串。猜猜为什么会这样? 我在这里添加了一个新问题,这样更容易获得积分:***.com/questions/61845865/…【参考方案2】:

您的应用组的网址在这里:

let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "SchoolCompanion)

sqlite 文件在那里。您不需要云容器。

【讨论】:

我的容器是 NSPersistentCloudKit 容器,这意味着它已同步到 iCloud。 在我帖子的链接中不是那样的。在这里,您将容器名称作为参数传递,但这应该是 appGroup 标识符,如所述

以上是关于带有核心数据的 iOS Today 扩展的主要内容,如果未能解决你的问题,请参考以下文章

iOS Today Extension - 共享核心数据

iOS扩展Extension之Today

适用于 iOS Today 扩展的 Firebase 分析(小部件扩展)

iOS 8 beta - Today 扩展无法识别嵌入式框架

如何在 Today 扩展中发送和接收数据

iOS Today 扩展神秘高度