I already have an iPhone App that stores data in a file in the local documents folder. Now I learnt about iCloud technologies and my first question was: is there a way to use iCloud as a directory when sometimes I check for new versions?
I mean: can I avoid using UIDocument, file coordinators and file presenters? I want just to know if could treat iCloud like a special folder and only use NSFileManager to push and retrieve files.
Last note: I don't use Core Data or any database, I only have a data file.
Edit:
I already read the official Apple iCloud documentation so don't link me to them. I only need some code examples.
When you first set up iCloud on a device, iCloud storage is automatically turned on for certain apps. Some iCloud features are also turned on automatically. You can turn these on or off at any time and customize the settings for each device. See Set up iCloud on all your devices.
On each device, go to Settings > iCloud > toggle on the app categories and content you want to sync to iCloud.
Make sure you're signed into the same Apple ID on both your iPhone and iPad. Open Documents on your iOS device. Drag and drop the file or folder you want to sync to the Documents in iCloud tab on your iPad or Documents in iCloud the folder on your iPhone.
I know how you feel, iCloud is a bit daunting. However, I think there is no way around UIDocument, file coordinators etc. and to simply use iCloud as a simple folder.
If you are looking for an easy to understand sample code, please have a look at this post:
iCloud basics and code sample
I included a full sample code which covers the bare minimums of iCloud and pretty much uses it like a directory. Perhaps this makes it less daunting for you to use UIDocument, file coordinators etc.
But, like you, I wish there was an easier and more compatible way with the good old documentary folder idea. However, as this is iCloud and as iCloud does several things more (like keeping everything in sync on different devices, constantly updating to cloud etc.), there will be no way around UIDocument etc.
What "works" for me is just simple:
NSFileManager *fm = [NSFileManager defaultManager]; NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; if (ubiq == nil) { return NO; } NSError *theError = nil; [fm setUbiquitous:true itemAtURL:backupUrl destinationURL:[[ubiq URLByAppendingPathComponent:@"Documents" isDirectory:true] URLByAppendingPathComponent:backupName] error:&theError];
Apple says to call on the non-UI thread. Having the files "moved". You can query for them via NSMetaDataQuery
like this:
self.query = [[NSMetadataQuery alloc] init]; [self.query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]]; NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K like '*.db'", NSMetadataItemFSNameKey]; [self.query setPredicate:pred]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:self.query]; [self.query startQuery]; - (void)queryDidFinishGathering:(NSNotification *)notification { NSMetadataQuery *query = [notification object]; [query disableUpdates]; [query stopQuery]; [self loadData:query]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query]; self.query = nil; }
Sample of enumeration through the query results:
- (void)loadData:(NSMetadataQuery *)query { [self.backups removeAllObjects]; for (NSMetadataItem *item in [query results]) { NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; [self.backups addObject:url.lastPathComponent]; } [_table reloadData]; [self.loadingBackupIndicator stopAnimating]; self.loadingIndicatorLabel.text = [NSString stringWithFormat: @"%d backups found", [self.backups count]]; }
And to start "download" of the concrete file:
NSFileManager *fm = [NSFileManager defaultManager]; NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; if (ubiq == nil) { return NO; } NSError *theError = nil; bool started = [fm startDownloadingUbiquitousItemAtURL:[[ubiq URLByAppendingPathComponent:@"Documents" isDirectory:true] URLByAppendingPathComponent:backupName] error:&theError]; NSLog(@"started download for %@ %d", backupName, started); if (theError != nil) { NSLog(@"iCloud error: %@", [theError localizedDescription]); }
With checks for file "being downloaded":
- (BOOL)downloadFileIfNotAvailable { NSNumber *isIniCloud = nil; NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSURL *file = [[ubiq URLByAppendingPathComponent:@"Documents" isDirectory:true] URLByAppendingPathComponent:self.backupName]; if ([file getResourceValue:&isIniCloud forKey:NSURLIsUbiquitousItemKey error:nil]) { // If the item is in iCloud, see if it is downloaded. if ([isIniCloud boolValue]) { NSNumber* isDownloaded = nil; if ([file getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) { if ([isDownloaded boolValue]) { [self.loadingBackupIndicator stopAnimating]; self.loadingIndicatorLabel.text = @"Downloaded"; .... [[NSFileManager defaultManager] copyItemAtPath:[file path] toPath:restorePath error:&theError ]; .... return YES; } self.loadingCheckTimer = [NSTimer timerWithTimeInterval:3.0f target:self selector:@selector(downloadFileIfNotAvailable) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:self.loadingCheckTimer forMode:NSDefaultRunLoopMode]; return NO; } } } return YES; }
I didn't expect the code to be that long and sorry for providing very raw snippets here. No intent to say the above can be a production quality of code, just sharing the concept.
I have not yet submitted that inside my app to Apple, so can't tell that would be "approved" to the app store (if they find or care...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With