'iOS Share Extension + NSFetchedResultsController not returning results - controllerDidChangeContent not called

I am sharing my CoreData model between my iOS main app target and a new Share Extension target like this post

This is working well for the most part except for one thing. NSFetchedResultsController is not returning results when called from the Shared Extension. What is strange though is that if I do a plain NSFetchRequest in my Share Extension, I do get CoreData results returned that were originally saved from the main app. I think Container setup as well as model must be being shared correctly via AppContainer.

NSFetchedResultsControllerDelegate controllerDidChangeContent is never called

ShareViewController:

import UIKit
import MobileCoreServices

class ShareViewController: UIViewController {

    private(set) lazy var resultsController: NSFetchedResultsController<Person> = createFetchedResultsController()

    override func viewDidLoad() {
        super.viewDidLoad()

        let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]

        do {
                /// this works!
            let persons = try CoreDataManager.shared.managedObjectContext.fetch(fetchRequest)
            print("Got \(persons.count) Persons")
        } catch {
            print("Fetch failed")
        }

        activateResultsController()
    }

    func createFetchedResultsController() -> NSFetchedResultsController<Person> {
        CoreDataManager.shared.container.viewContext.stalenessInterval = 0
        CoreDataManager.shared.container.viewContext.refreshAllObjects()
        CoreDataManager.shared.container.viewContext.stalenessInterval = -1

        let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]

        ////managedObjectContext: CoreDataManager.shared.managedObjectContext,
        let controller = NSFetchedResultsController(
                fetchRequest: fetchRequest,
                managedObjectContext: CoreDataManager.shared.managedObjectContext,
                sectionNameKeyPath: nil,
                cacheName: nil
        )
        controller.delegate = self

        return controller
    }

    private func activateResultsController() {
        do {
            try resultsController.performFetch()
        } catch {
            fatalError("Failed to fetch entities: \(error)")
        }
    }
}


// MARK: - Results Controller Delegate

extension ShareViewController: NSFetchedResultsControllerDelegate {

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        guard let sections = resultsController.sections else {
            return
        }

        let section = sections[0]
        let rows = section.numberOfObjects
        print("rows=\(rows)")
    }
}

CoreDataManager:

import UIKit
import CoreData

class CoreDataManager {

    static let shared = CoreDataManager()

    internal var container: NSPersistentContainer

    var managedObjectContext: NSManagedObjectContext {
        container.viewContext
    }

    init() {
        container = NSPersistentContainer(name: Constants.name)

        guard let storeDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
            // We'll throw a fatalError() because we can't really proceed without storeDirectory
            fatalError(file: "Could not find .applicationSupportDirectory - exiting")
        }

        let storeURL = storeURL(for: "group.mygroup.testshareextensioncoredata", databaseName: "\(Constants.name)")
        let storeDescription = NSPersistentStoreDescription(url: storeURL)
        container.persistentStoreDescriptions = [storeDescription]

        container.loadPersistentStores(completionHandler: { storeDescription, error in
            if let error = error as NSError? {
                // We'll throw a fatalError() because we can't really proceed without loading the PersistentStore
                fatalError("loadPersistentStore failed \(error), \(error.userInfo)")
            }
        })
    }

    // MARK: - Core Data Saving support

    func saveContext() {
        managedObjectContext.performAndWait {
            if managedObjectContext.hasChanges {
                do {
                    try managedObjectContext.save()
                } catch {
                }
            }
        }
    }


    /// Returns a URL for the given app group and database pointing to the sqlite database.
    func storeURL(for appGroup: String, databaseName: String) -> URL {
        guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
            fatalError("Shared file container could not be created.")
        }

        return fileContainer.appendingPathComponent("\(databaseName).sqlite")
    }
}


internal extension CoreDataManager {
    enum Constants {
        static let name = "ShareExtensionCoreDataTest"
    }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source