- Setting Up an iCloud-Compatible Project
- How iCloud Works
- iCloud Container Folders
- Recipe: Trying Out iCloud
- Working with UIDocument
- Recipe: Subclassing UIDocument
- Metadata Queries and the Cloud
- Handy Routines
- Recipe: Accessing the Ubiquitous Key-Value Store
- Recipe: UIManagedDocument and Core Data
- Summary
Handy Routines
This section presents a number of extremely simple routines that work together to allow you to manage files across your sandbox and cloud Documents folders. These methods assume your files live at the top level of these folders for several reasons:
- Both the container and sandbox Documents folders are made accessible to your users via Settings and iTunes. Therefore, they are directly visible.
- Neither system supports subfolders at this time.
- Your users own these folders directly, according to Apple’s guidelines, and should have full access and visibility to all materials contained within.
Local URLs
Working with the sandbox Documents folder involves little more than querying for the documents directory. These methods return the documents folder and URLs that point inside that folder. This localFileURL: method does not test the URL it returns to see if it points to a valid object.
+ (NSString *) localDocumentsPath { return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; } + (NSURL *) localDocumentsURL { return [NSURL fileURLWithPath:[self localDocumentsPath]]; } + (NSURL *) localFileURL: (NSString *) filename { if (!filename) return nil; NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename]; return fileURL; }
Locating the Ubiquitous Documents Folder
Supply a container identifier and NSFileManager returns URLs pointing to the root of that cloud folder. Appending Documents produces the ubiquitous documents URL.
+ (NSURL *) ubiquityDataURLForContainer: (NSString *) container { return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container]; } + (NSURL *) ubiquityDocumentsURLForContainer: (NSString *) container { return [[self ubiquityDataURLForContainer:container] URLByAppendingPathComponent:@"Documents"]; }
When you have that folder’s URL, you can append file names to it to point to items within the Documents subfolder. Like the localFileURL: method, the following methods do not test the URL it returns to see if it points to a valid object:
+ (NSURL *) ubiquityDataFileURL: (NSString *) filename forContainer: (NSString *) container { if (!filename) return nil; NSURL *fileURL = [[self ubiquityDataURLForContainer:container] URLByAppendingPathComponent:filename]; return fileURL; } + (NSURL *) ubiquityDocumentsFileURL: (NSString *) filename forContainer: (NSString *) container { if (!filename) return nil; NSURL *fileURL = [[self ubiquityDocumentsURLForContainer:container] URLByAppendingPathComponent:filename]; return fileURL; }
Retrieving Files
The first three of these methods test whether a file exists, locally or in the data or documents ubiquity. The final method uses these tests to retrieve a file URL, searching first the local folder and then the cloud. If a matching file is found, it returns that URL.
+ (BOOL) isLocal: (NSString *) filename { if (!filename) return NO; NSURL *targetURL = [self localFileURL:filename]; if (!targetURL) return NO; return [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path]; } + (BOOL) isUbiquitousData: (NSString *) filename forContainer: (NSString *) container { if (!filename) return NO; NSURL *targetURL = [self ubiquityDataFileURL:filename forContainer:container]; if (!targetURL) return NO; return [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path]; } + (BOOL) isUbiquitousDocument: (NSString *) filename forContainer: (NSString *) container { if (!filename) return NO; NSURL *targetURL = [self ubiquityDocumentsFileURL:filename forContainer:container]; if (!targetURL) return NO; return [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path]; } + (NSURL *) fileURL: (NSString *) filename forContainer:(NSString *)container { if ([self isLocal:filename]) return [self localFileURL:filename]; if ([self isUbiquitousDocument:filename forContainer:container]) return [self ubiquityDocumentsFileURL:filename forContainer:container]; if ([self isUbiquitousData:filename forContainer:container]) return [self ubiquityDataFileURL:filename forContainer:container]; return nil; }
Setting Ubiquity
The following method applies ubiquity to a file based on its name, not its current location. This routine checks for each possible file condition. If the file is already found where it’s supposed to end up, the method returns with success. If the file cannot be found, it fails. Otherwise, the code moves the file to or from the cloud as requested, wrapping away the otherwise fussy implementation details of the native NSFileManager method.
+ (BOOL) setUbiquitous:(BOOL)yorn for:(NSString *)filename forContainer:(NSString *)container { if (!filename) return NO; NSError *error; NSURL *localURL = [self localFileURL:filename]; NSURL *ubiquityURL = [self ubiquityDocumentsFileURL:filename forContainer:container]; BOOL localFound = [self isLocal:filename]; BOOL ubiquityFound = [self isUbiquitousDocument:filename forContainer:container]; // Check file not found if (!localFound && !ubiquityFound) return NO; // Check the two "nothing to be done" cases if (!yorn && localFound) return YES; if (yorn && ubiquityFound) return YES; // ubiquitous to local if (!yorn) { // Move file away from cloud if (![[NSFileManager defaultManager] setUbiquitous:NO itemAtURL:ubiquityURL destinationURL:localURL error:&error]) { NSLog(@"Error removing %@ from %@ storage: %@", filename, container, error.localizedFailureReason); return NO; } return YES; } // local to ubiquitous if (![[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:localURL destinationURL:ubiquityURL error:&error]) { NSLog(@"Error moving %@ to %@ storage: %@", filename, container, error.localizedFailureReason); return NO; } return YES; }
Deleting Files
These methods take three approaches to deleting files. The first deletes a local copy at the top of the Documents sandbox folder. The second moves items from the cloud (data and documents) and then deletes it locally. The third finds the file first and then invokes one of the previous solutions.
+ (BOOL) deleteLocal: (NSString *) filename { NSURL *targetURL = [self localFileURL:filename]; if (![[NSFileManager defaultManager] fileExistsAtPath:targetURL.path]) { NSLog(@"Local file not found: %@", filename); return NO; } NSError *error; BOOL success = [[NSFileManager defaultManager] removeItemAtURL:targetURL error:&error]; if (!success) NSLog(@"Error removing file %@: %@", filename, error.localizedFailureReason); return success; } + (BOOL) deleteUbiquitousDocument:(NSString *)filename forContainer:(NSString *)container { NSURL *targetURL = [self ubiquityDocumentsFileURL:filename forContainer:container]; if (![[NSFileManager defaultManager] fileExistsAtPath:targetURL.path]) { NSLog(@"Ubiquitous file not found: %@", filename); return NO; } // Remove from ubiquity and then delete BOOL success = [self setUbiquitous:NO for:filename forContainer:container]; if (success) return [self deleteLocal:filename]; return NO; } + (BOOL) deleteUbiquitousData:(NSString *)filename forContainer:(NSString *)container { NSError *error; BOOL success; NSURL *targetURL = [self ubiquityDataFileURL:filename forContainer:container]; success = [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path]; if (!success) { NSLog(@"Ubiquitous file not found: %@", filename); return NO; } success = [[NSFileManager defaultManager] removeItemAtURL:targetURL error:&error]; if (!success) { NSLog(@"Could not remove item at path: %@", error.localizedFailureReason); return NO; } return YES; } + (BOOL) deleteDocument: (NSString *) filename forContainer:(NSString *)container { // If local, delete it. if ([self isLocal:filename]) return [self deleteLocal:filename]; return [self deleteUbiquitousDocument:filename forContainer:container]; }
Retrieving Modification Dates
These methods retrieve the modification date for a file, in the top level of either Documents folder (sandbox or ubiquitous), depending on where the file is found.
+ (NSDate *) modificationDateForURL:(NSURL *) targetURL { if (!targetURL) return nil; NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:targetURL.path error:nil]; if (!attributes) return nil; return [attributes fileModificationDate]; } + (NSDate *) modificationDateForFile: (NSString *) filename { if (!filename) return nil; return [self modificationDateForURL: [self fileURL:filename]]; } + (NSTimeInterval) timeIntervalSinceModification: (NSString *) filename { return [[NSDate date] timeIntervalSinceDate: [self modificationDateForFile:filename]]; }