Saving a file to icloud drive

7.2k views Asked by At

I'm trying to save a file to icloud drive. I'm going for the simple version where I don't let the user pick where to save the file, I just save it in the root directory of icloud drive. Here's the code I'm using:

func exportToFiles() {
    for page in pages {
            let filename = getDocumentsDirectory().appendingPathComponent((page.title ?? "Untitled") + ".png")
        if let image = exportImage(page: page, dpi: 300) {
            if let data = image.pngData() {
                try? data.write(to: filename)
            }
        }
    }
}

func getDocumentsDirectory() -> URL {
    let driveURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
    return driveURL!
}

And I have this in my Info.plist file:

<key>NSUbiquitousContainers</key>
<dict>
        <key>iCloud.com.mysite.myapp</key>
        <dict>
                <key>NSUbiquitousContainerIsDocumentScopePublic</key>
                <true/>
                <key>NSUbiquitousContainerName</key>
                <string>myapp</string>
                <key>NSUbiquitousContainerSupportedFolderLevels</key>
                <string>Any</string>
        </dict>
</dict>

In the UI it looks like this works, because there's a slight pause after I tap my button. But when I look in my files, there's nothing there. What am I doing wrong?

3

There are 3 answers

3
iUrii On BEST ANSWER

You have to check and create "Documents" folder and also it's good practice to handle errors:

if let containerUrl = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") {
    if !FileManager.default.fileExists(atPath: containerUrl.path, isDirectory: nil) {
        do {
            try FileManager.default.createDirectory(at: containerUrl, withIntermediateDirectories: true, attributes: nil)
        }
        catch {
            print(error.localizedDescription)
        }
    }
    
    let fileUrl = containerUrl.appendingPathComponent("hello.txt")
    do {
        try "Hello iCloud!".write(to: fileUrl, atomically: true, encoding: .utf8)
    }
    catch {
        print(error.localizedDescription)
    }
}
0
Honey On

Please double check that in the "Signing & Capabilities" tab, you have checked "iCloud Documents" option in iCloud tab. And also created container with exactly same name - "myapp" like you specified in your plist file.

<key>NSUbiquitousContainerName</key>
<string>myapp</string>

And what is the return value of exportImage(page: page, dpi: 300)? Please make sure that it's returning UIImage object.

2
bruce1337 On

To update this for mid-2022 with SwiftUI, Swift 5, Xcode 13.2.1, iOS 15.2:

  • Right under your iOS app target's "Signing & Capabilities" tab, click the "+ Capability" button. In the dialog that appears, scroll down in the left to find "iCloud" and double-click it.
  • Back under "Signing & Capabilities" a new section will have appeared. Check "iCloud Documents" and beneath that look for the "Containers" list. In the first entry there, copy and paste the value from "Bundle Identifier" (near the top of the same pane). Edit the container entry so that it is in the format iCloud.domain.orgName.appName. For example, if your bundle identifier is foo.bar.bazApp, that first entry in the Containers list should be iCloud.foo.bar.bazApp. After doing this, that first entry should be checked.
  • If you do not have a .plist file, then you'll need to make one. It should be the first or second file in the navigator pane, and its name should take the form of bazApp--iOS--Info. Click on it to edit it. It should look something like this - note that the very last part of your container name needs to be an exact match under NSUbiquitousContainerName. Continuing with the iCloud.foo.bar.bazApp example here:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>NSUbiquitousContainers</key>
  <dict>
      <key>iCloud.foo.bar.bazApp</key>
      <dict>
          <key>NSUbiquitousContainerIsDocumentScopePublic</key>
          <true/>
          <key>NSUbiquitousContainerName</key>
          <string>bazApp</string>
          <key>NSUbiquitousContainerSupportedFolderLevels</key>
          <string>Any</string>
      </dict>
  </dict>
</dict>
</plist>
  • Inside your actual app code, use examples like the ones listed above to write a test file.
  • Under your iOS target's "General" tab, in the "Identity" section, increment the Version and Build fields, if you have made any changes to your .plist file.

If you have set everything up correctly, then in phone or simulator's Files app > iCloud Drive, you should see a new folder with your app name and app icon, just like any other app-specific iCloud folder. Inside should be your new test file. If, like me, you did not enter the NSUbiquitousContainerName value in the .plist exactly, your code will run, but the test file will only be written to Settings > [Your Name] > iCloud > Manage Storage > [Your App Name] > Documents. Some other answers on here suggest that NSUbiquitousContainerName can be any name you like, but my experience was that it has to be an exact match to the ending portion of the container name.