10

I am using the UIDocumentPicker to select a file but if it's large it can take a while to open and that is not a particularly good experience for users.

I have looked at the iCloud programming guide from Apple and I cannot seem to figure out how to actually download a file and get some progress feedback, the documentation is just too vague. I know I am supposed to do something with NSMetadataItems, but there isn't much explaining actually how to get one and use it.

Can someone please explain it to me?

P.S. can someone with higher rep than me tag this post with UIDocumentPicker and iCloudDrive?

Weston
  • 1,481
  • 1
  • 11
  • 31

2 Answers2

13

To my knowledge, you can only retrieve progress feedback using the Ubiquitous Item Downloading Status Constants which provides only 3 states:

  • NSURLUbiquitousItemDownloadingStatusCurrent
  • NSURLUbiquitousItemDownloadingStatusDownloaded
  • NSURLUbiquitousItemDownloadingStatusNotDownloaded

So you can't have a quantified progress feedback, only partial aka downloaded or not.

To do so, you need to prepare and start your NSMetadataQuery, add some observers and check the downloading status of your NSMetadataItem using the NSURLUbiquitousItemDownloadingStatusKey key.

self.query = [NSMetadataQuery new];
self.query.searchScopes = @[ NSMetadataQueryUbiquitousDocumentsScope ];
self.query.predicate = [NSPredicate predicateWithFormat:@"%K like '*.yourextension'", NSMetadataItemFSNameKey];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:nil];

[self.query startQuery];

Then,

- (void)queryDidUpdate:(NSNotification *)notification
{
    [self.query disableUpdates];

    for (NSMetadataItem *item in [self.query results]) {
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
        NSError *error = nil;
        NSString *downloadingStatus = nil;

        if ([url getResourceValue:&downloadingStatus forKey:NSURLUbiquitousItemDownloadingStatusKey error:&error] == YES) {
            if ([downloadingStatus isEqualToString:NSURLUbiquitousItemDownloadingStatusNotDownloaded] == YES) {
                // Download
            }
            // etc.
        }
    }

    [self.query enableUpdates];
}
HiDeoo
  • 10,353
  • 8
  • 47
  • 47
  • so, if I want to find a specific file, do I just use that in the predicate? – Weston Nov 06 '14 at 23:19
  • Yeah, you can look for a specific name, all files with your extension, path, size, creation date or anything defined in the [attribute keys](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSMetadataItem_Class/index.html#//apple_ref/doc/constant_group/Attribute_Keys) from NSMetadataItem. I edited my answer to add an example. – HiDeoo Nov 06 '14 at 23:29
  • One more question, why do you turn off updates, then turn them back on? – Weston Nov 06 '14 at 23:30
  • 1
    We need to disable / enable updates before iterating over query results because they could change due to live updates. From the documentation: "Unless you use enumerateResultsUsingBlock: or enumerateResultsWithOptions:usingBlock:, you should invoke this method" – HiDeoo Nov 06 '14 at 23:34
3

Progress feedback from an NSMetadataQuery happens through notifications. The update frequency is once per second per default (can be changed by setting notificationBatchingInterval). The updated objects are encapsulated in the userInfo dict of the notification as arrays of NSMetadataItem. Download feedback is encapsultaed in the key NSMetadataUbiquitousItemPercentDownloadedKey of each item. Since the arrays are internally mutable, we need to tell NSMetadataQuery to disable updates while we enumerate the results. This is important, otherwise strange crashes will occur.

A typical implementation might look like this:

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

[self.mdQuery disableUpdates];// we don't want to receive a new update while we still process the old one

NSArray *addedItems     = notification.userInfo[NSMetadataQueryUpdateAddedItemsKey];
NSArray *remItems       = notification.userInfo[NSMetadataQueryUpdateRemovedItemsKey];
NSArray *changedItems   = notification.userInfo[NSMetadataQueryUpdateChangedItemsKey];

// add
for (NSMetadataItem *mdItem in addedItems) {
    NSURL *url          = [mdItem valueForKey:NSMetadataUbiquitousItemURLInLocalContainerKey];
    // do something...
}
// remove
for (NSMetadataItem *mdItem in remItems) {
    NSURL *url          = [mdItem valueForKey:NSMetadataUbiquitousItemURLInLocalContainerKey];
    // do something...
}
// change
for (NSMetadataItem *mdItem in changedItems) {
    NSURL *url          = [mdItem valueForKey:NSMetadataUbiquitousItemURLInLocalContainerKey];
        // uploading
        BOOL uploading  = [(NSNumber *)[mdItem valueForKey:NSMetadataUbiquitousItemIsUploadingKey] boolValue];
        if (uploading) {
            NSNumber *percent   = [mdItem valueForKey:NSMetadataUbiquitousItemPercentUploadedKey];
            cell.progressView.progress = percent.floatValue;
            // do something...
        }
        // downloading
        BOOL downloading    = [(NSNumber *)[mdItem valueForKey:NSMetadataUbiquitousItemIsDownloadingKey] boolValue];
        if (downloading) {
            NSNumber *percent   = [mdItem valueForKey:NSMetadataUbiquitousItemPercentDownloadedKey];
            cell.progressView.progress = percent.floatValue;
            // do something...
        }
    }
[self.mdQuery enableUpdates];
}
Mojo66
  • 1,109
  • 12
  • 21