2

I set up an App Group to use with my Today Extension. I added the app group in the main app's target and in the extension's target. In my App ID Configuration in the developer portal I have the app group enabled. But for some reason FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: id) returns nil whenever I call it. I have tripled checked the forSecurityApplicationGroupIdentifier string and know it's correct.

Does anyone have any idea why this is not working?

EDIT: I was able to fix the nil issue by editing my entitlements for debug to include the app group. I have been following this post and was able to successfully migrate my data from my NSPersistentContainer, but this same method does not work when I try to use an NSPersistentCloudKitContainer for when the user has toggled iCloud on. It's able to still migrate the data, but does not let me sync my data between devices (pretty much just makes it become a regular NSPersistentContainer). If I revert back to the old way of how I was doing this I am able to use iCloud sync.

Can anyone help me fix this syncing issue when migrating with a NSPersistentCloudKitContainer to use the app group?

Core Data code:

class CoreDataManager {
    static let sharedManager = CoreDataManager()
    private init() {}

    lazy var persistentContainer: NSPersistentContainer = {
        var useCloudSync = UserDefaults.standard.bool(forKey: "useCloudSync")

        //Get the correct container
        let containerToUse: NSPersistentContainer?
        if useCloudSync {
           containerToUse = NSPersistentCloudKitContainer(name: "App")
        } else {
            containerToUse = NSPersistentContainer(name: "App")      
        }

        guard let container = containerToUse else {
            fatalError("Couldn't get a container")
        }

        //Set the storeDescription
        let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.App")!.appendingPathComponent("\(container.name).sqlite")

        var defaultURL: URL?
        if let storeDescription = container.persistentStoreDescriptions.first, let url = storeDescription.url {
            defaultURL = FileManager.default.fileExists(atPath: url.path) ? url : nil
        }

        if defaultURL == nil {
            container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
        }


        let description = container.persistentStoreDescriptions.first else {
            fatalError("Hey Listen! ###\(#function): Failed to retrieve a persistent store description.")
        }

        description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        if !useCloudSync {
            description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        }

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in

            //migrate from old url to use app groups
            if let url = defaultURL, url.absoluteString != storeURL.absoluteString {
                let coordinator = container.persistentStoreCoordinator
                if let oldStore = coordinator.persistentStore(for: url) {
                    do {
                        try coordinator.migratePersistentStore(oldStore, to: storeURL, options: nil, withType: NSSQLiteStoreType)
                    } catch {
                        print("Hey Listen! Error migrating persistent store")
                        print(error.localizedDescription)
                    }

                    // delete old store
                    let fileCoordinator = NSFileCoordinator(filePresenter: nil)
                    fileCoordinator.coordinate(writingItemAt: url, options: .forDeleting, error: nil, byAccessor: { url in
                        do {
                            try FileManager.default.removeItem(at: url)
                        } catch {
                            print("Hey Listen! Error deleting old persistent store")
                            print(error.localizedDescription)
                        }
                    })
                }
            }
         }

         return container
   }
}

I assumed the issue was with the store descriptions, but when I look at the storeDescription before and after changing it, its options still have iCloud sync on.

fphelp
  • 1,544
  • 1
  • 15
  • 34
  • If you change the place of your container, you’ll lose the old data. So you need to migrate your data at first launch of your app. – Mannopson May 17 '20 at 05:02
  • @Mannopson How can I migrate the data over? I have been looking for a solution for the past couple of hours and can't find anything. Please help – fphelp May 17 '20 at 05:06
  • There is a lot of online tutorials, you can easily find them. – Mannopson May 17 '20 at 05:10
  • Haven't been able to find any that have been useful or simple to understand. Any suggestions ? – fphelp May 17 '20 at 05:12
  • The first problem is a shared database, as you said it is a `nil`. How to initialize your container? Any code would be useful. – Mannopson May 17 '20 at 05:16
  • I just edited my post to show my code – fphelp May 17 '20 at 05:22
  • You’ll need a three line of code to share your database with the extension. A cloud sync option is not useful if you use the iCloud, because the user can change it from Settings->iCloud. `if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.organization.app")?.appendingPathComponent("App.sqlite") { let persistentStoreDescription = NSPersistentStoreDescription.init(url: url) container.persistentStoreDescriptions = [persistentStoreDescription] }` – Mannopson May 17 '20 at 05:44
  • I added that and am having the same issue with not seeing data made before the app group. But what I noticed was when I append the new `persistentStoreDescription ` instead of doing `container.persistentStoreDescriptions = [persistentStoreDescription]`, I can see the data from before. The only problem was is that I got this error: `CoreData: error: Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store` which did not allow me to update or add data. Any suggestions about next steps? We're close – fphelp May 17 '20 at 06:18
  • As I said you will lose your old data as soon as container changed. – Mannopson May 17 '20 at 10:35
  • @Mannopson I was able to migrate the data of the `NSPersistentContainer` but am having syncing issues with the `NSPersistentCloudKitContainer`. I updated my answer. I believe we're close to figuring this out! – fphelp May 17 '20 at 17:18
  • Did you ever get around to resolving this issue? I'm having the same problem with things not syncing to iCloud after the migration. – Gian Sep 10 '20 at 18:50

2 Answers2

6

I had the same issue in the past. To fix FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: id) being nil, just make sure that the entitlements that are being used for the debug (You can find this in your target's Build Settings) has the app group you made. Sometimes it's not added.

ap123
  • 916
  • 1
  • 8
  • 22
  • This works for fixing the nil error. Do you happen to know how to fix the cloud syncing issue? – fphelp May 17 '20 at 17:41
2

I had the nil problem on containerURL when I compiled my project (originally written on an Intel MBP and all files were copied directly) on a shiny, new M1 Mac. The solution appears to have been that I needed to manually enter a new group into the entitlements.

Perhaps the file just needed a refresh....

EDIT: It's become weird! I've found that the containerURL(forSecurityApplicationGroupIdentifier: bundleID) is a bit funny about capital letters(!). When

bundleID = "com.MyGroup.container" (or, indeed, if it's all lower cased) then it returns a useable URL despite the group being "com.MyGroup.Container".

but when

bundleID = "com.MyGroup.Container" (exactly as per entitlesments capitalisation) then it returns nil

I've now started using:

containerURL(forSecurityApplicationGroupIdentifier: Bundle.main.bundleIdentifier!.lowercased())

Todd
  • 1,770
  • 1
  • 17
  • 42