6

I used as described in the Apple Docs NSMetadataQuery to search my iCloud file. I have only one file and I know its name. My problem is that sometimes this file doesn't exist (I guess because it has not yet been downloaded) and NSMetadataQuery is unable to find it.

Ever tried to force download with NSFileManager startDownloadingUbiquitousItemAtURL:error: and it returns me an error. (Read EDIT)

My solution is that I created the file the first time, then I guess it exists and I open it with UIDocument. But It couldn't exist or it could be the first time the user opens the app. I can't be sure of these things. My first question is: if UIDocument opens the file, it means that it found the file somewhere. How could it use the file if it DOESN'T EXIST?

And then, second question: If I app which has to manage multiple files or files with unknown name. How can I find them if NSMetadataQuery doesn't work.

EDIT: if startDownloadingUbiquitousItemAtURL should be used to start downloading a file, how can I know when the file finished downloading (perhaps with a notification)? But, a more important thing: How can I download the file if is always says (removed original names)?

   Error Domain=NSPOSIXErrorDomain Code=2 "The operation couldn’t be completed.
    No such file or directory" UserInfo=0x166cb0 {
    NSDescription=Unable to get real path for Path
'/private/var/mobile/Library/Mobile Documents/teamid~com~team~app/Documents/file.extension'
    }
albianto
  • 4,092
  • 2
  • 36
  • 54

3 Answers3

7

I suggest the following load routine for iCloud files. It involves 4 steps:

  1. First, testing if iCloud is accessible
  2. then look for your files (either look for a specific file like you indicated or for all files with a certain extension like *.txt, or if you don't really know what file extension you are looking for, something like NSPredicate *pred = [NSPredicate predicateWithFormat:@"NOT %K.pathExtension = '.'", NSMetadataItemFSNameKey]; will return all files which have an extension, like jpg, txt, dat etc.)
  3. then checking if the query is done, and
  4. finally attempt to load the file. If the file doesn't exist, create it. If it does exist, load it.

Here is the code that exemplifies these four steps:

    - (void)loadData:(NSMetadataQuery *)query {

    // (4) iCloud: the heart of the load mechanism: if texts was found, open it and put it into _document; if not create it an then put it into _document

    if ([query resultCount] == 1) {
        // found the file in iCloud
        NSMetadataItem *item = [query resultAtIndex:0];
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];

        MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:url];
        //_document = doc;
        doc.delegate = self.viewController;
        self.viewController.document = doc;

        [doc openWithCompletionHandler:^(BOOL success) {
            if (success) {
                NSLog(@"AppDelegate: existing document opened from iCloud");
            } else {
                NSLog(@"AppDelegate: existing document failed to open from iCloud");
            }
        }];
    } else {
        // Nothing in iCloud: create a container for file and give it URL
        NSLog(@"AppDelegate: ocument not found in iCloud.");

        NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
        NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:@"Documents"] URLByAppendingPathComponent:@"text.txt"];

        MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:ubiquitousPackage];
        //_document = doc;
        doc.delegate = self.viewController;
        self.viewController.document = doc;

        [doc saveToURL:[doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            NSLog(@"AppDelegate: new document save to iCloud");
            [doc openWithCompletionHandler:^(BOOL success) {
                NSLog(@"AppDelegate: new document opened from iCloud");
            }];
        }];
    }
}

- (void)queryDidFinishGathering:(NSNotification *)notification {

    // (3) if Query is finished, this will send the result (i.e. either it found our text.dat or it didn't) to the next function

    NSMetadataQuery *query = [notification object];
    [query disableUpdates];
    [query stopQuery];

    [self loadData:query];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
    _query = nil; // we're done with it
}

-(void)loadDocument {

    // (2) iCloud query: Looks if there exists a file called text.txt in the cloud

    NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
    _query = query;
    //SCOPE
    [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
    //PREDICATE
    NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, @"text.txt"];
    [query setPredicate:pred];
    //FINISHED?
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
    [query startQuery];

}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"AppDelegate: app did finish launching");
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

    // Override point for customization after application launch.
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil] autorelease];
    } else {
        self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil] autorelease];
    }

    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];

    // (1) iCloud: init

    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    if (ubiq) {
        NSLog(@"AppDelegate: iCloud access!");
        [self loadDocument];
    } else {
        NSLog(@"AppDelegate: No iCloud access (either you are using simulator or, if you are on your phone, you should check settings");
    }


    return YES;
}
n.evermind
  • 11,944
  • 19
  • 78
  • 122
  • I'm already doing this. When I started using iCloud the first thing I did, was reading all your posts. – albianto Oct 31 '11 at 09:25
  • In that case, I think I don't really understand your question. You have a file which may exist or may not exist (e.g. "theFile.txt"). With the above code you can search for theFile.txt; If it doesn't exist, it will create it. If it does exist, it will load it. – n.evermind Oct 31 '11 at 11:26
  • The file doesn't exist and I create it on Device 1. Then I open it, do some changes and close. Then I open the app on Device 2 and it sometimes finds the file and sometimes it doesn't. – albianto Oct 31 '11 at 11:51
  • I see, that's frustrating. Can you post your code as it is really hard to reconstruct what the problem is without actually seeing some code. – n.evermind Oct 31 '11 at 14:07
  • I use the same code as above, placed in different places but it's the same. The problem is how can I download the file if it says it doesn't exist. How much I have to wait before metadata is synced, so that NSMetadataQuery can know the existence of the file? – albianto Oct 31 '11 at 17:06
  • Well, you execute a query (which doesn't download anything) and you have to wait until the query is done. This is the reason for [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query]; [query startQuery]; – n.evermind Oct 31 '11 at 17:40
  • Once the query is done, you load the results of the query. Depending on the results (either file is found or file is not found), you then attempt to load the file (the downloading bit is done by iCloud). I'd recommend you start with a new template and simply try to load a simple text file. See if this works and if you can get my sample code to work. Once you're done with this, try and see where the error is in your code right now. – n.evermind Oct 31 '11 at 17:42
  • Because the query sometimes return empty result but the file has just been created by the other device – albianto Nov 01 '11 at 05:34
  • 1
    Found the problem!! I was using file coordinators in another part of code and I guess this caused some troubles to the UIDocument file coordinators. – albianto Nov 01 '11 at 06:10
  • 1
    I had this same error while I was developing my app and testing my app iCloud setup screens over and over and over, by deleting the files from the devices and My Mac and I somehow corrupted the file store in iCloud so I had to loop through the files in the ubiquitous folder and delete it all manually from both devices, then delete app, delete anything that might show in settings /icloud /manage docs/Myapp and then re installed the dev app on both devices, and all works again. Good luck to anyone else that has this issue. – J3RM Feb 13 '12 at 20:37
1

edo42, did you solve the issue? I had purchased a new 4S to go back to AT&T and I had backed up my first 4S and then restored from backup on new 4S and then this was happening for me.

  1. When I do a [iCloudFileURL getResource:&fileState forKey:NSURLIsUbiquitousItemKey error:&error] I get the same error.

Error Domain=NSPOSIXErrorDomain Code=2 "The operation couldn’t be completed. No such file or directory" UserInfo=0x448500 {NSDescription=Unable to get real path for Path '/private/var/mobile/Library/Mobile Documents/~UbiquitousDir~/Documents/MyDocument.ext'

(Works fine on my iPad 2)

  1. Then I would do the NSMetadataQuery but then it comes back with no results. (Works on my iPad 2 just fine to see the document on iCloud).

  2. I checked to see that the ubiquitous file exists but it is not there as the error suggest.

  3. I tried using [fileMgr evictUbiquitousItemAtURL:ubiquitousDocumentsDir &error] and it does succeed. But when I try the NSMetadataQuery it still comes back with no results.

Hope this helps to figure what is wrong in both cases.

UPDATE: Ok there is a real problem with iCloud for this device that I restored from a backup:

  1. I have deleted my app from the iPhone device.
  2. I deleted the backup files for my app from Manage Storage.
  3. I have even delete the file for my app from iCloud (Documents & Data) under Managed storage.
  4. The iCloud file is no longer there and I look on iPad 2 and it's gone.
  5. I started my app on my iPad 2 and he successfully saves a new iCloud document.
  6. The new iCloud file shows on my iPhone.
  7. I build and run my app on my device but he cannot download the file with NSMetadaQuery query.
Community
  • 1
  • 1
  • One thing I did notice after the restore from backup is that my app was not the compiled version I had recently ran on the device just days ago. So I compiled the latest and now getting this. – Kenneth Lewis Nov 30 '11 at 14:43
  • Also after evicting the ubiquitous Documents directory I deleted both the Documents directory and the ubiquitous directory and still not able to download the file on iCloud. – Kenneth Lewis Nov 30 '11 at 14:45
  • Also the file is showing under iCloud in Settings/Storage & Backup/Manage Storage/Documents & Data. – Kenneth Lewis Nov 30 '11 at 14:46
0

I had a similar problem, metadataQueryDidUpdate and metadataQueryDidFinishGathering would fire, but the NSMetadataQuery's results would be empty. There was a provisioning profile problem that Xcode didn't tell me about until I attempted to test it on my device, where it failed, saying that my bundle identifier was invalid (it wasn't).

What fixed it for me was going into Preferences and clicking the Download All button for the provisioning profiles, and then doing a Clean in Xcode. Rebuild, run, and all my files showed up. Check in the developer portal to make sure none of your profiles are marked as Invalid. None of mine were, so it's not required to trigger this, but it can happen.

Not exactly scientific, but this did work for me.

stevex
  • 5,589
  • 37
  • 52