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

12

33

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).

Carmine answered 8/8, 2014 at 12:36 Comment(2)
Just trying to make sure: You are not using "com.apple.TextEdit" itself as the key in the Info.plist, right? You should be using the value of com.apple.developer.icloud-container-identifiers you specified in the entitlements file.Dartboard
Yap, I'm using our own container identifier.Carmine
C
9

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.

Carmine answered 19/8, 2014 at 9:9 Comment(3)
You need to have NSUbiquitousContainerIsDocumentScopePublic=true in Info.plist before you run the app for the first time.Seller
The main point I marked in bold, have to add 'NSUbiquitousContainerIsDocumentScopePublic=true' in Info.plist before you run the app for the first time otherwise the folder will be hidden by default, not a public folder. If already run the app before adding this code to info.plist then uninstall the app, change the build number and run again.Seller
I see, thanks for clarification. I must admit I haven't looked into this in a while, but it does sound plausible. Thank you :)Carmine
H
21

I was experiencing a similar problem with my application. I was able to make this work by doing the following:

  1. Add the NSUbiquitousContainers setting to my Info.plist file according to documentation here https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/FileProvider.html. Here is the relevant code:

    <dict>
        <!-- ... other top-level Info.plist settings ... -->
        <key>NSUbiquitousContainers</key>
        <dict>
            <key>iCloud.com.example.MyApp</key>
            <dict>
                <key>NSUbiquitousContainerIsDocumentScopePublic</key>
                <true/>
                <key>NSUbiquitousContainerSupportedFolderLevels</key>
                <string>Any</string>
                <key>NSUbiquitousContainerName</key>
                <string>MyApp</string>
            </dict>
        </dict>
    </dict>
    
  2. Important! I then changed the above NSUbiquitousContainerSupportedFolderLevels string value from Any to One

    <key>NSUbiquitousContainerSupportedFolderLevels</key>
    <string>One</string>
    
  3. Next, and last, I had to change CFBundleVersion to a higher version. I also bumped the CFBundleShortVersionString to a new version as well.

Built and ran and after that, the folder with my applications icon appeared properly in iCloud Drive! Hope this helps!

Hertel answered 27/4, 2015 at 3:42 Comment(3)
Why was it necessary to set the SupportedFolderLevels to "Any" first before setting it to "One"?Cohosh
It seems that setting the supported folder level to One is not necessary. Bumping the bundle version number is the key thingIncompliant
This is what solved it for me! Awesome, thanks.Gelsemium
D
17

When you edited the Info.plist, maybe you forgot to bump up the bundle version number? This is a requirement as per WWDC session #234.

Dartboard answered 15/8, 2014 at 15:2 Comment(6)
I didn't know that, so thanks for the hint and the session link. However, we have bumped the version number several times (for other reasons), so this doesn't cut it either.Carmine
Again, just trying to make sure: Did you change the value of CFBundleVersion to a higher number?Dartboard
I'll double-check on Monday, but I'm almost 100% sure we did.Carmine
We've increased the version number several times since the change, and I just played around with reverting/re-applying the changes and bumping the bundle version number, but without success.Carmine
Can't think of anything else that might cause the problem. FWIW, here is my source code that does work for me: github.com/roop/GiveAndTakeDartboard
THANK YOU! This was crucial.Scoles
C
9

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.

Carmine answered 19/8, 2014 at 9:9 Comment(3)
You need to have NSUbiquitousContainerIsDocumentScopePublic=true in Info.plist before you run the app for the first time.Seller
The main point I marked in bold, have to add 'NSUbiquitousContainerIsDocumentScopePublic=true' in Info.plist before you run the app for the first time otherwise the folder will be hidden by default, not a public folder. If already run the app before adding this code to info.plist then uninstall the app, change the build number and run again.Seller
I see, thanks for clarification. I must admit I haven't looked into this in a while, but it does sound plausible. Thank you :)Carmine
T
5

It seems, changing the CFBundleVersion will let it work.

I think you can try it. I got this from Apple Developer Forums.

Hope this work for you.

Transferor answered 3/10, 2016 at 6:36 Comment(2)
I am also able to turn the visibiliy of the iCloud container on and off by changing NSUbiquitousContainerIsDocumentScopePublic and then bumping CFBundleVersion to tell macos that a change has taken place. This reliably triggers a change in the folder visibility.Whitethorn
Yes @patrickkidd, turning NSUbiquitousContainerIsDocumentScopePublic off with version bump then on again with version bump really works. Much the simplest solution.Twopence
B
3

After dorking around with this all morning, reading all the posts, making all the changes, the key thing that finally worked for me was, as Yet Another Code Maker stated, changing the bundle ID. I think once it has created a container for a bundle, you can't go back and change the visibility of it to have it appear in Finder. I had tried all the different info.plist values but nothing worked until I changed to a new bundle name and forced the system to create a new one. By the way, I didn't see this noted anywhere but the bundle name, the NSUbiquitousContainer name and the NSUbiquitousContainerName can all be different - which is what I did in my case. After spending so much time on this, I figured I would go ahead and put a simple sample app on GitHub in case anyone is still having problems debugging their iCloud folder appearing in Finder - you can find it here. All the required steps are outlined in the README.

Biology answered 12/3, 2017 at 19:55 Comment(0)
W
2

In my case (Xcode 7 and iOS 9), the only thing which made it works, after multiple tries, was just use a new bundle identifier (you don't have to change the cloud container identifier, just be sure to select the container you want to use in the Apple Developer Member Centre and to specify in Xcode a custom container instead of the default).

In fact, that means the first time you run your application, the NSUbiquitousContainers section of the info.plist has to be set up. If you set it afterwards as a second step, it won't work...

Whirlwind answered 26/9, 2015 at 20:5 Comment(2)
Absolutely correct! Solved my problem after so many days of struggle. Thanks!!Twopence
IIRC You have to bump version to make it re-scan the changes in plist.Silverweed
W
2

Well, it's not documented anywhere but try to add Documents folder in the container and store your files there.

Found this hint in replies in this Apple Developer Forum thread.

Walter answered 11/2, 2020 at 17:44 Comment(1)
This fixed the issue for me. Instead of appending "Documents" I had appended "MyAppName". After changing to "Documents", it works.Dogma
A
1

The .plist entry on this documentation page has an additional entry:

<key>NSUbiquitousContainerName</key>
<string>MyApp</string>

Maybe the missing name is prohibiting it from showing up.

Aquiline answered 8/8, 2014 at 13:7 Comment(2)
Unfortunately, that doesn't help. According to the doc I've linked, it's optional: "NSUbiquitousContainerName (String - iOS and OS X) Specifies the name that the iCloud Drive displays for your container. By default, the iCloud Drive will use the name of the bundle that owns the container." I've added it nonetheless, but without any effect.Carmine
Yeah, I read that. But I’d figure it was worth a try anyway. The same question is also at devforums.apple.com but there’s no answer yet. So I’d say it’s a bug or not yet available. For me iCloudDrive is still wonky in Yosemite… :/Aquiline
H
1

Couldn't find any documentation, but trial and error, I found that:

[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"com.apple.CloudDocs"]; 

Gives you the base URL for the drive as seen in the picker. Using this base URL I was able to save files in my app and see it on the iCloud drive within Yosemite.

Edit 14.8.14

I tried your plist settings:

<key>NSUbiquitousContainers</key>
<dict>
    <key>iCloud.net.redacted.docTest</key>
    <dict>
        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
        <true/>
        <key>NSUbiquitousContainerSupportedFolderLevels</key>
        <string>Any</string>
    </dict>
</dict>

In my little throwaway test app "docTest" it does indeed expose the empty Documents directory in Yosemite and in the document picker.

Screenshot http://spring-appstudio.com/picker-view.png

Heated answered 13/8, 2014 at 20:15 Comment(3)
I'm not trying to save regular files in iCloud Drive - that works fine with the Document Picker, too. I'm trying to expose the existing Documents directory that is associated with my app. The directory exists (I can verify that programmatically or in the Terminal on Yosemite), but it's hidden in Apple's standard UI (Finder and Document Picker).Carmine
Your Info.plist additions worked for me. Details above.Heated
Thanks for trying that out. I assume I'm hitting a bug for some reason, but the general approach seems to be correct.Carmine
J
1

Just wanted to emphasize one of the OP's discoveries that fixed it for me:

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.

You need to hard code the bundle id. Also update the version.

(I didn't notice this in the question until I went through all the answers).

Jellied answered 28/4, 2016 at 16:42 Comment(0)
L
0

Same problem occurred to my OSX app.

It is seemed that NSUbiquitousContainers setting works only in the creation time of the iCloud containers. So I tried with new Apple ID(for preparing clean iCloud environment), it becomes to work.

Leeuwenhoek answered 2/9, 2014 at 4:6 Comment(0)
G
0

I know this is an old thread but just in case someone runs into the same issue: Only way for me to get my Container folder to be visible in iCloud Drive (after trying all the above suggestions) was to have my app create a temporary file in the Documents folder. As soon as I did that the Container Folder (and the file I created) showed up on my Mac. If this is really the case that I have to create a file to make this folder visible then this would be a bit annoying because my app is a read only app (only reads files added by the user to the Container Folder). The Container Folder needs to be visible as soon as the app is launched for the first time. I guess I will have to detect the first launch.

Geomancy answered 5/1, 2018 at 17:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.