3

wonder if anyone can help me. I have an app and I'm trying to move some files into iCloud so they'll show up in "Files" and cloud to other devices. I've been going through lots of resources online researching what's wrong, and nothing seems to help.

In my app project, I have turned on iCloud Documents in capabilities.

In my plist file, I have this:

<key>NSUbiquitousContainers</key>
    <dict>
        <key>iCloud.com.mypublishername.myappname</key>
        <dict>
            <key>NSUbiquitousContainerIsDocumentScopePublic</key>
            <true/>
            <key>NSUbiquitousContainerName</key>
            <string>myappname</string>
            <key>NSUbiquitousContainerSupportedFolderLevels</key>
            <string>Any</string>
        </dict>
    </dict>

In my entitlements file I have:

<dict>
    <key>com.apple.developer.icloud-container-identifiers</key>
    <array>
        <string>iCloud.com.mypublishername.myappname</string>
    </array>
    <key>com.apple.developer.icloud-services</key>
    <array>
        <string>CloudDocuments</string>
    </array>
    <key>com.apple.developer.ubiquity-container-identifiers</key>
    <array>
        <string>iCloud.com.mypublishername.myappname</string>
    </array>
</dict>

in ObjC, I'm fetching the iCloud folder like so:

NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:@"Documents"];
if (rootDirectory)
{
    if (![[NSFileManager defaultManager] fileExistsAtPath:rootDirectory.path isDirectory:nil]) [[NSFileManager defaultManager] createDirectoryAtURL:rootDirectory withIntermediateDirectories:YES attributes:nil error:nil];
    gCloudFolder=rootDirectory;
}

Then, when I save a file, I do so locally, and move it into the cloud folder like this:

//
// theFilename is a file in the app's documents folder...
//
int aFile=creat(theFilename,S_IREAD|S_IWRITE);close(aFile);
aFile=open(theFilename,O_BINARY|O_RDWR);
if (aFile)
{
    write(aFile,theDataPtr,theLen);
    close(aFile);

    if (gCloudFolder)
    {
        NSURL *aLocalStr=[NSURL fileURLWithPath:[NSString stringWithUTF8String:theFilename]];
        NSURL *aCloudStr=[gCloudFolder URLByAppendingPathComponent:@"testing_file.txt"];
                
        NSError *error;
        if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:aLocalStr destinationURL:aCloudStr error:&error]) NSLog(@"iCloud Error occurred: %@", error);
    }

So... what happens. This file DOES get created. If I run this twice, it tells me it can't move to testing_file.txt because it already exists. Also, if I try to setUbiquitous:NO on the file, it tells me I can't set it to no when the file hasn't been synced.

Any idea why my app's folder and this file don't show up in my FILES folder under iCloud?

I have increased the bundle version, which is something I've seen elsewhere. Did nothing.

What am I doing wrong?

John Raptis
  • 177
  • 7
  • It's not? I keep running into this code when I google online how to drop stuff into the files app. Can you advise on where I look to store data in iCloud? I'm wanting to dump documents into a folder that is available across all systems. – John Raptis Dec 15 '20 at 14:30
  • This and other articles: https://dev.to/nemecek_f/ios-saving-files-into-user-s-icloud-drive-using-filemanager-4kpm ... in the files app I have "iCloud Drive" which is where I want the user's data files to go. – John Raptis Dec 15 '20 at 14:42
  • ??? NSURL *rootDirectory = [[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]URLByAppendingPathComponent:@"Documents"]; <-- I have it right there. – John Raptis Dec 15 '20 at 14:50
  • Mmm right. Well, as I say, I don't believe in this method of communicating with the Files app. But I'll try it out! – matt Dec 15 '20 at 14:52
  • Matt, I am only using this code because it's what I've seen recommended on the web. I have a bunch of files that the app uses in the NSDocuments folder, and I essentially want to mirror them in iCloud Files whenever they "save." Is there a better way to do that? I'm not married to this method! – John Raptis Dec 15 '20 at 14:56
  • Well that's a poke in the eye for me, it works! Son of a gun. I did have to increment the bundle version and the whole thing sprang to life. – matt Dec 15 '20 at 15:58
  • Also I had to slap the Files app in the side of the head (by killing it) in order to see anything happen. – matt Dec 15 '20 at 16:07

1 Answers1

2

This completely stunned me; I had no idea it was possible. I'll just describe my test app in full. It's going to look a lot like yours!

Here is the bundle identifier:

enter image description here

Here is the entitlement:

enter image description here

Here is the entitlement text:

<?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>com.apple.developer.icloud-container-identifiers</key>
    <array>
        <string>iCloud.com.neuburg.matt.SaveIntoFilesApp</string>
    </array>
    <key>com.apple.developer.icloud-services</key>
    <array>
        <string>CloudDocuments</string>
    </array>
    <key>com.apple.developer.ubiquity-container-identifiers</key>
    <array>
        <string>iCloud.com.neuburg.matt.SaveIntoFilesApp</string>
    </array>
</dict>
</plist>

Here is the entry in the Info.plist:

<key>NSUbiquitousContainers</key>
<dict>
    <key>iCloud.com.neuburg.matt.SaveIntoFilesApp</key>
    <dict>
        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
        <true/>
        <key>NSUbiquitousContainerName</key>
        <string>MyApp</string>
        <key>NSUbiquitousContainerSupportedFolderLevels</key>
        <string>Any</string>
    </dict>
</dict>

Here is the app delegate:

var ubiq : URL!

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    DispatchQueue.global(qos:.default).async {
        let fm = FileManager.default
        let ubiq = fm.url(forUbiquityContainerIdentifier:nil)
        print("ubiq: \(ubiq as Any)")
        DispatchQueue.main.async {
            self.ubiq = ubiq
        }
    }

    return true
}

Here is the button I tap:

@IBAction func doButton (_ sender:Any) {
    if let del = UIApplication.shared.delegate as? AppDelegate {
        if let ubiq = del.ubiq {
            do {
                let fm = FileManager.default
                let docs = ubiq.appendingPathComponent("Documents")
                try? fm.createDirectory(at: docs, withIntermediateDirectories: false, attributes: nil)
                let url = docs.appendingPathComponent("test.txt")
                print("here we go")
                try? fm.removeItem(at: url)
                try "howdy \(Date())".write(to: url, atomically: true, encoding: .utf8)
                print("saved")
                
            } catch {
                print(error)
            }
        }
    }
}

I did have to increment the bundle version (from 1 to 2) and I did have to kill and restart the Files app. And then I saw my file (and can open and examine it):

enter image description here

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Guess what. Killing the Files app and opening it again was the answer. I spent nearly eight hours on this. – John Raptis Dec 15 '20 at 17:09
  • Yay!!!! Thank you for asking this question, I learned something. – matt Dec 15 '20 at 17:21
  • I think the reason you have to kill the Files app is that this is not how you're supposed to put things in the ubiquity container. You're supposed to put _documents_ in there, using UIDocument. When you do, other apps (like the Files app) that are subscribing to your stuff are given a signal that something has changed (NSFileCoordinator, NSFilePresenter, etc.). But you and I are not doing that; we are operating behind the Files app's back. – matt Dec 15 '20 at 17:24
  • BTW, Matt, it looks like even this much work is not necessary. Fiddling around a little, if you just get a path to the cloud folder and do this: int aFile=creat(theFilename,S_IREAD|S_IWRITE);close(aFile);aFile=open(theFilename,O_BINARY|O_RDWR);write(aFile,theData,theDataLen);close(aFile); ... it all just works too. For us minimalists who hate Obj-C :) – John Raptis Dec 17 '20 at 16:24
  • But the Documents folder is necessary, right? That was the part I didn't know about when all this started...! – matt Dec 17 '20 at 18:50
  • Yep-- if you create /Documents, then anything you put in there clouds automatically. I don't know why this isn't just said anywhere, that simply, on the web! I was doing some crazy roundabout things to accomplish this. – John Raptis Dec 18 '20 at 02:20
  • Yep if you look at my Swift code that’s all I’m doing, writing a string straight into documents folder – matt Dec 18 '20 at 03:34