iOS今日扩展和核心数据

6

我正在尝试为我的iOS应用制作一个今日扩展。 该扩展将显示基于使用核心数据保存的数据的“下一个”课程。 我已经做了一些研究,我明白我必须与应用组共享我的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")
    }
}

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

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

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


你能解决如何将数据连接到你的今日小组件吗?我目前也遇到了同样的问题。 - fphelp
@fphelp 你正在使用 CloudKit 容器吗? - Cydiaddict
当用户打开iCloud时,我使用CloudKit容器;当用户关闭iCloud时,我使用常规的NSPersistentContainer。 - fphelp
你可以使用云容器进行两者操作,尝试添加以下代码: yourStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: "yourContainerID") - Cydiaddict
2个回答

4
你应该像下面这样子类化NSPersistentCloudKitContainer,为defaultDirectoryURL()返回应用组URL。然后在你的CoreDataStack中,使用let container = GroupedPersistentCloudKitContainer(name: "SchoolCompanion")。还要删除对addToAppGroup(...)的调用。你需要在应用程序和扩展中实例化GroupedPersistentCloudKitContainer,并确保GroupedPersistentCloudKitContainer链接到两个目标。
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
@fphelp 我没有见过那个错误,但是它听起来像是你在你的扩展中声明了这个类。请在你的应用程序中声明它,并通过将目标设置为两个来确保它在你的扩展中可以访问。 - hidden-username
我曾经因为太难而放弃了这个小部件,但现在我又回来了,并不得不重新编写所有代码。我刚刚将GroupedPersistentCloudKitContainer添加到我的代码中,但当我尝试运行应用程序时,在if!FileManager.default.fileExists(atPath:url!.path)一行出现错误,说无法解包url。我认为这意味着url为空。我三次检查过了,我正在使用URLStrings.group.rawValue的正确字符串。你有什么猜测为什么会发生这种情况吗? - fphelp
我在这里添加了一个新问题,这样更容易,你也可以获得积分:https://dev59.com/80sGtIcB2Jgan1zncsMP - fphelp

1

您的应用组的URL在此处:

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

sqlite文件已经存在,你不需要云容器。


我的容器是一个NSPersistentCloudKit容器,这意味着它与iCloud同步。 - user10768342
在我发布的链接中不是这样的。在这里,您将容器名称作为参数传递,但应该是appGroup标识符,就像描述的那样。 - user10768342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接