iosxcodeicloudmarmalademarmalade-edk

How can I add iCloud Documents capabilities to my application without using Xcode?


My app crashes with an "Application initializing document picker is missing the iCloud entitlement" when either of the following two lines is executed:

UIDocumentPickerViewController* documentPicker =
  [[UIDocumentPickerViewController alloc]
    initWithDocumentTypes:@[@"public.data"]
                   inMode:UIDocumentPickerModeImport];

UIDocumentMenuViewController *documentMenu =
  [[UIDocumentMenuViewController alloc]
    initWithDocumentTypes:@[@"public.data"]
                   inMode:UIDocumentPickerModeImport];

The Document Picker Programming Guide states that "Before your app can use the document picker, you must turn on the iCloud Documents capabilities in Xcode."

However, my app is not built with Xcode: it is built using third-party tools (the cross-platform toolkit, Marmalade), so I cannot do this.

It should still be possible to turn on iCloud Documents capabilities for this app manually — the switch in iCloud simply automates the process — but my attempts to do so have not fixed the crash.

What I've tried so far

Xcode displays the steps it carries out when switching on iCloud:

  1. Add the "iCloud" entitlement to your App ID
  2. Add the "iCloud containers" entitlement to your App ID
  3. Add the "iCloud" entitlement to your entitlements file
  4. Link CloudKit.framework

I also found Apple's Entitlements Troubleshooting TechNote, which describes steps that can be taken to check that the following steps have been carried out correctly.

I have enabled iCloud on my App ID:

Screenshot of App ID showing iCloud is enabled

I'm not certain whether this is necessary to use the document picker's simple import and export operations, but I also set up an iCloud container with the id iCloud.com.[company].[app].

I have generated an updated provisioning profile that includes the iCloud entitlements:

Screenshot of Provisioning Profile showing iCloud Service is enabled

I inspected the downloaded provisioning profile using the command:

security cms -D -i /path/to/iOSTeamProfile.mobileprovision

It includes the following entries:

<key>com.apple.developer.icloud-services</key>
<string>*</string>
<key>com.apple.developer.icloud-container-environment</key>
<array>
  <string>Development</string>
  <string>Production</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
  <string>iCloud.com.[company].[app]</string>
</array>
<key>com.apple.developer.icloud-container-development-container-identifiers</key>
<array>
  <string>iCloud.com.[company].[app]</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
  <string>iCloud.com.[company].[app]</string>
</array>

Marmalade uses this provisioning profile to generate the entitlements file for the app.

I have inspected the generated entitlements using the following command:

codesign -d --ent :- [App.app]

Which gives the following output:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>application-identifier</key>
  <string>[team-id].com.[company].[app]</string>
  <key>aps-environment</key>
  <string>development</string>
  <key>com.apple.developer.icloud-container-development-container-identifiers</key>
  <array>
    <string>iCloud.com.[company].[app]</string>
  </array>
  <key>com.apple.developer.icloud-container-environment</key>
  <array>
    <string>Development</string>
    <string>Production</string>
  </array>
  <key>com.apple.developer.icloud-container-identifiers</key>
  <array>
    <string>iCloud.com.[company].[app]</string>
  </array>
  <key>com.apple.developer.icloud-services</key>
  <string>*</string>
  <key>com.apple.developer.team-identifier</key>
  <string>[team-id]</string>
  <key>com.apple.developer.ubiquity-container-identifiers</key>
  <array>
    <string>iCloud.com.[company].[app]</string>
  </array>
  <key>com.apple.developer.ubiquity-kvstore-identifier</key>
  <string>[team-id].*</string>
  <key>get-task-allow</key>
  <true/>
  <key>keychain-access-groups</key>
  <array>
    <string>[team-id].com.[company].[app]</string>
  </array>
</dict>
</plist>

However, the app still crashes whenever the functions are called.

I also found this old guide to setting up iCloud in Marmalade apps. Most of the steps seem no longer to be necessary/possible, but I followed the suggestion to add the application-identifier key to my Info.plist.

What else do I need to do to add iCloud Documents capabilities to my app?


Solution

  • Marmalade generates the .xcent entitlements file used when signing the app by copying over the "Entitlements" dict from the provisioning profile.

    The problem was caused by the value associated with the undocumented key:

    <key>com.apple.developer.icloud-services</key>
    <string>*</string>
    

    This appears to be valid in the provisioning profile, but invalid when signed into the app. Replacing those elements in the generated .xcent file with the following and then re-signing the app fixed the issue:

    <key>com.apple.developer.icloud-services</key>
    <array>
      <string>CloudDocuments</string>
    </array>
    

    (N.B. If you also use CloudKit, you will also need to add a CloudKit string to the array.)

    In practice, we fixed this by editing Marmalade's sign_app.py script to use a pre-prepared .xcent file (copied over from the DerivedData directory of the functioning app we built with Xcode) when signing the app:

    In file /Applications/Marmalade.app/Contents/s3e/deploy/plugins/iphone edit line 557:

    cmd += ['--entitlements', xcentfile.name]
    

    ...replacing xcentfile.name with the path to the pre-prepared .xcent file.