Search code examples
icloudios8entitlementsicloud-apiinfo-plist

Exposing an app's ubiquitous container to iCloud Drive in iOS 8


I'm developing an iCloud-enabled app where users will be able to import and export files via iCloud Drive. When browsing iCloud Drive, either using the UIDocumentPickerViewController (iOS 8) or the Finder (OS X Yosemite), I can see directories created/owned by other iCloud-Drive-enabled apps, such as, Automator, Keynote, or TextEdit.

I want our app to expose its ubiquitous documents directory in iCloud Drive, too, but haven't been able to figure it out yet. Within some of the aforementioned apps' Info.plist files, I've discovered this key:

<key>NSUbiquitousContainers</key>
<dict>
    <key>com.apple.TextEdit</key>
    <dict>
        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
        <true/>
        <key>NSUbiquitousContainerSupportedFolderLevels</key>
        <string>Any</string>
    </dict>
</dict>

These keys are also documented here, but I haven't found any other documentation on the broader subject. Edit/Note: Although it does not contain the answer to my questions, the Document Picker Programming Guide is a helpful resource.

I've tried adding the above-mentioned keys/values to our app but didn't see any effect. Things I've noticed/tried:

  • For 3rd party apps, iCloud containers are constructed this way: iCloud.$(CFBundleIdentifier). I'm not sure why TextEdit only uses the pure bundle identifier, but for our identifier, I've tried both approaches, i.e., with and without the iCloud. prefix. I've also recognised that you need to hard-code the bundle identifier (i.e., don't use iCloud.$(CFBundleIdentifier)) as only the PLIST's values seem to be resolved at build time, but not the keys.

  • I've added a sub-directory programmatically (to <containerPath>/Documents) so the container is not empty. However, this shouldn't matter as all the other apps' directories were initially empty, too.

  • Some Apple apps that appear in iCloud Drive do not have these entries in their Info.plist, e.g., Numbers and Pages.

  • iCloud is set up correctly and I can programmatically look into the ubiquity container using the URL returned by [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];.

  • I am logged into an iCloud account where iCloud Drive is enabled. I can see my iCloud Drive content in the UIDocumentPickerViewController.

  • I use the iOS 8 beta 5 simulator (and Yosemite beta 5 to view the iCloud Drive directory on the Mac) (Edit/Note: This equally applies to beta 6)

This is how my Entitlements file looks like (relevant parts only)

<key>com.apple.developer.icloud-container-identifiers</key>
<array>
    <string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
    <string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array/>

I've set this up using Xcode's UI in the Capabilities section. I don't get why the last key doesn't have an entry, but adding <string>iCloud.$(CFBundleIdentifier)</string> doesn't help. Instead, it makes Xcode complain in the Capabilities UI, so I've removed it. Edit/Note: In Xcode beta 6, this has been fixed, i.e., the ubiquity container identifier needs to be set and Xcode can fix that for you.

Original Questions: So... is it a bug? Does it not work yet? Am I doing it wrong? I couldn't find a known issue in the release notes.

Edit:

Two more things that I've tried:

  • Adding the (optional) NSUbiquitousContainerName key (+ value) to the container-specific dictionary, as suggested by Erikmitk.

  • Adding only the NSUbiquitousContainerIsDocumentScopePublic key/value to the PLIST root dictionary rather than the container-specific dictionary, as it's done in one of the WWDC sample apps (look for NewBox).


Solution

  • The catch is to call [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; (or with another container identifier if it's not the default one) at least once (not per launch, but presumably per version, or when changing one of the respective PLIST entries) in order to initialize the directory. I reckon this step needs to be combined with an increase of the bundle version number, as suggested in roop's answer.

    I notice my question may have been confusing in that respect, as I mentioned being able to look into the documents directory* programmatically using the API in question. However, I removed that code from the app later, maybe before getting the rest of the setup right. I'm not going to write into the documents directory directly, only through the Document Picker. Therefore, there hasn't been any need to get the URL.

    If you just need a Document Picker to read/store files from/in iCloud Drive or other apps' document directories, there's no need to call URLForUbiquityContainerIdentifier:. Only if you want your app to have its own ubiquity container (and potentially expose it in iCloud Drive and the Document Picker), the steps mentioned in the original post and the call to URLForUbiquityContainerIdentifier: are necessary.

    *When mentioning the documents directory, I'm always referring to the one in the ubiquity container, not the local one.