Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apple iCloud implementation on Mac apps

Tags:

macos

icloud

I'm aware that iOS has API's to integrate iCloud for the apps. Can I integrate iCloud in mac apps as well? Will the implementation be different for Mac apps to integrate iCloud? If Yes, are there any tutorials etc or reference websites?

like image 577
Satyam Avatar asked Dec 04 '11 14:12

Satyam


2 Answers

Yes. iCloud is available on the Mac.
But Apple's documentation for this topic still isn't very complete. The only official resources I could find where the WWDC 2011 session 107 video and some notes in "What's new in Mac OS X"

When Lion & iCloud still were under NDA I posted my findings in Apple's devforums.
This is is an edited version of this post:

I am using a modified version of the WWDC 2011 Session 107 code. (transcribed from the video) I had to remove the manual instantiation of NSFileCoordinator to get the sample working (the speaker mentions that the coordinator "might not be needed in the future"):

- (IBAction)moveToOrFromCloud:(id)sender
{
     NSFileManager* fm = [NSFileManager defaultManager];
     NSURL* fileURL = [[self document] fileURL];
     BOOL shouldMakeUbiquitous = [sender tag] == 1;
     NSURL* destinationURL;
     if(shouldMakeUbiquitous)
     {
          NSURL* rootURL = [fm URLForUbiquityContainerIdentifier:@"app.example"];
          NSURL* directoryURL = [rootURL URLByAppendingPathComponent:@"Documents"];
          [fm createDirectoryAtURL:directoryURL withIntermediateDirectories:NO attributes:nil error:NULL];
          destinationURL = [directoryURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
     }
     else
     {
          destinationURL = [[[fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0] URLByAppendingPathComponent:[fileURL lastPathComponent]];
     }
     NSError* error;
     if(![fm setUbiquitous:shouldMakeUbiquitous itemAtURL:fileURL destinationURL:destinationURL error:&error])
     {
          [[self document] presentError:error modalForWindow:[[self document] windowForSheet] delegate:nil didPresentSelector:NULL contextInfo:NULL];
     }
     else
     {
          [[self document] setFileURL:destinationURL];
          [[self document] setFileModificationDate:nil];
     }
}

The above IBAction is connected to a NSMenuItem that checks if the document is already in iCloud or if it needs to be uploaded:

- (BOOL)validateMenuItem:(NSMenuItem*)item 
{
     SEL action = [item action];
     if (action == @selector(moveToOrFromCloud:))
     {
          BOOL isUbiquitous = [[NSFileManager defaultManager] isUbiquitousItemAtURL:[[self document] fileURL]];
          [item setTitle:isUbiquitous ? @"Remove from Cloud": "Move to Cloud"];
          [item setTag:isUbiquitous?0:1];
          return [self.document fileURL] != nil;
     }    
     return YES;
}

Checklist for non-code tasks that are necessary to get iCloud document storage working:

  • Check if iCloud support is activated in the Developer Certificate Utility
  • Create a ubiquity container ID in the Developer Certificate Utility
  • The Ubiquity container ID starts with your Team ID/Individual ID (see the account tab in member center)
  • Enable Entitlements in Xcode
  • Add your ubiquity container ID to the entitlements file (as described here “Requesting Entitlements for iCloud Storage.”)
  • My plist Bundle ID had to match the ubiquity container ID (except for the Team ID)
  • I was not able to add suffixes (e.g. "app.example.osx", "app.example.ipad", ... as suggested in the doc above)
  • Create a provisioning profile
  • Make sure the profile is installed on your dev machine and shows up in Xcode & System Preferences
  • Enable code signing in your apps build settings
like image 173
Thomas Zoechling Avatar answered Nov 09 '22 11:11

Thomas Zoechling


There is a document by Apple that is prety detailed in all aspects http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/iCloud/iCloud.html

One significant issue with the code posted here is that it does not have the team in the identifier given to URLForUbiquityContainerIdentifier even if it is mentioned in the checklist, leaving it nil altogether to be automatically filled from the entitlements seems like the best aproach.

Personally the only changes i had to do to get iCloud going in my app were :

  1. check "use iCloud" button on the developer website for my app id
  2. download the regenerated provision for that app id
  3. check "enable entitlements" in xcode summary

That was all, here is a hopefully clearer sample code (should work for both iOS and OSX) :

NSURL *url = [self getiCloudURLFor:@"foo.bar" containerID:nil]; //leaving nil so it is auto filled from entitlements
if (url) {
    NSError *error;
    if (![[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:url error:&error]) {
        NSLog(@"Error downloading/syncing %@ (%@)",[url path],[error description]);                
    }else{
        NSLog(@"Started downloading/syncing %@",[url path]);              
    }         
}

NSArray *conflicts = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:url];
for (NSFileVersion *conflict in conflicts) {
    NSLog(@"Conflicting %@ at %@ by %@ from %@",[url path],[conflict URL],[conflict localizedNameOfSavingComputer],[conflict modificationDate]);   
}

- (NSURL*)getiCloudURLFor:(NSString*)fileName containerID:(NSString*)containerID
{   
    NSFileManager *fm = [NSFileManager defaultManager];  

    NSURL *rootURL = [fm URLForUbiquityContainerIdentifier:containerID];
    if (rootURL) {
        NSURL *directoryURL = [rootURL URLByAppendingPathComponent:@"Documents"];
        if (![fm fileExistsAtPath:[directoryURL path]]) [fm createDirectoryAtURL:directoryURL withIntermediateDirectories:NO attributes:nil error:NULL];
        NSURL *cloudURL = [directoryURL URLByAppendingPathComponent:fileName];
        if (![fm isUbiquitousItemAtURL:cloudURL]) [self makeUbiquitousItemAtURL:cloudURL];//this only runs once per filename when it is first added to iCloud
        return cloudURL;
    }else{
        return [[[fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0] URLByAppendingPathComponent:fileName]; //no cloud
    }     
    return nil;
}

- (void)makeUbiquitousItemAtURL:(NSURL*)cloudURL
{
    NSFileManager *fm = [NSFileManager defaultManager];

    NSURL *localURL = [[[fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0] URLByAppendingPathComponent:[cloudURL lastPathComponent]]; 
    if (![fm fileExistsAtPath:[localURL path]]) [fm createFileAtPath:[localURL path] contents:nil attributes:nil];
    NSError *error;            
    if(![fm setUbiquitous:YES itemAtURL:localURL destinationURL:cloudURL error:&error])  {
        NSLog(@"Error making %@ ubiquituous at %@ (%@)",[localURL path],[cloudURL path],[error description]);
    }else{
        NSLog(@"Made %@ ubiquituous at %@",[localURL lastPathComponent],[cloudURL path]);               
    }      
}
like image 25
valexa Avatar answered Nov 09 '22 11:11

valexa