Refael.S
Refael.S

Reputation: 1644

How to Sync an NSDocument from Mac osx to iPad/iPhone with iCloud

I have seen iCloud Document sample code for iOS and I used it to sync a to and from iCloud and now I am trying to sync iCloud with an on a Mac OSX app which doesn't have UIDocument.

I tried to change UIDocument to NSDocument but all the methods of syncing with are different. I haven't found any sample code or tutorials except for the documentation from which is very confusing and not well written.

For example, the method below, from UIDocument on iOS does not exist in NSDocument on OS X:

//doc is an instance of subclassed UIDocument
[doc openWithCompletionHandler:nil];

The Apple Documentation provides this code for OS X:

- (void)checkIfCloudAvaliable {
    NSURL *ubiquityContainerURL = [[[NSFileManager defaultManager]
                                    URLForUbiquityContainerIdentifier:nil]
                                   URLByAppendingPathComponent:@"Documents"];
    if (ubiquityContainerURL == nil) {
        NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                              NSLocalizedString(@"iCloud does not appear to be configured.", @""),
                              NSLocalizedFailureReasonErrorKey, nil];
        NSError *error = [NSError errorWithDomain:@"Application" code:404
                                         userInfo:dict];
        [self presentError:error modalForWindow:[self windowForSheet] delegate:nil
        didPresentSelector:NULL contextInfo:NULL];
        return;
    }
    dest = [ubiquityContainerURL URLByAppendingPathComponent:
            [[self fileURL] lastPathComponent]];
}

- (void)moveToOrFromCloud {
    dispatch_queue_t globalQueue =
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalQueue, ^(void) {
        NSFileManager *fileManager = [[NSFileManager alloc] init];
        NSError *error = nil;
        // Move the file.
        BOOL success = [fileManager setUbiquitous:YES itemAtURL:[self fileURL]
                                   destinationURL:dest error:&error];
        dispatch_async(dispatch_get_main_queue(), ^(void) {
            if (! success) {
                [self presentError:error modalForWindow:[self windowForSheet]
                          delegate:nil didPresentSelector:NULL contextInfo:NULL];
            }
        });
    });
    [self setFileURL:dest];
    [self setFileModificationDate:nil];
}

How can I sync between iOS and OS X (because NSDocument does not exists on iOS, and UIDocument does not exist on OS X)? Does anyone know where I can find a sample for Mac OSX (NSDocument syncing)?

Upvotes: 4

Views: 4988

Answers (3)

Refael.S
Refael.S

Reputation: 1644

I managed to get it working! Here's my code from the subclassed file on OS X:

Header file:

#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>

@interface subclassedNSDocument : NSDocument

@property (strong) NSData *myData;

@end

Implementation file:

- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError 
{
    BOOL readSuccess = NO;
    if (data) 
    {
        readSuccess = YES;
        [self setMyData:data];
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:@"dataModified" 
                                                        object:self];

    return readSuccess;
}

- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError 
{
    if (!myData && outError) {
        *outError = [NSError errorWithDomain:NSCocoaErrorDomain
                                        code:NSFileWriteUnknownError userInfo:nil];
    }
    return myData;
}

and in the AppDelegate.m file:

#define kFILENAME @"mydocument.dox"

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSURL *ubiq = [[NSFileManager defaultManager] 
                   URLForUbiquityContainerIdentifier:nil];
    if (ubiq) {
        NSLog(@"iCloud access at %@", ubiq);
        // TODO: Load document... 
        [self loadDocument];
    }
    else 
    {
        NSLog(@"No iCloud access");
    }

    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(dataReloaded:) 
                                                 name:@"dataModified" object:nil];
}

- (void)update_iCloud
{
    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:@"Documents"] URLByAppendingPathComponent:kFILENAME];
    self.doc.myData = [NSKeyedArchiver archivedDataWithRootObject:[@"Your Data Array or any data", nil]];
    [self.doc saveToURL:ubiquitousPackage ofType:@"dox" forSaveOperation:NSSaveOperation error:nil];
}

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

    if ([query resultCount] == 1) {

        NSMetadataItem *item = [query resultAtIndex:0];
        NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
        NSLog(@"url = %@",url);
        subclassedNSDocument *doc = [[subclassedNSDocument alloc] initWithContentsOfURL:url ofType:@"dox" error:nil];
        [doc setFileURL:url];
        self.doc = doc;
    } 
    else {

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

        dataUrls *doc = [[dataUrls alloc] init];
        [self.doc setFileURL:ubiquitousPackage];
        self.doc = doc;
        [self.doc saveToURL:ubiquitousPackage ofType:@"dox" forSaveOperation:NSSaveOperation error:nil];
    }

}

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

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

    [[NSNotificationCenter defaultCenter] removeObserver:self 
                                                    name:NSMetadataQueryDidFinishGatheringNotification
                                                  object:query];

    _query = nil;

    [self loadData:query];

}

- (void)loadDocument {

    NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
    _query = query;
    [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
    NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, kFILENAME];
    [query setPredicate:pred];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];

    [query startQuery];

}

- (void)dataReloaded:(NSNotification *)notification 
{
    self.doc = notification.object;

    NSArray *arrFromCloud = [NSKeyedUnarchiver unarchiveObjectWithData:self.doc.myData];

    //Update you UI with new data
}

The only thing that I haven't got working is that if I change the data of the document on the iPad, the Mac app doesn't call the readFromData method for to update from iCloud, does anyone know what I am missing?

On iOS, the equivalent method, loadFromContents, is called automatically on every change of the UIDocument in iCloud. On OS X the readFromData is called once on load but never called again.

Hope my code can help, for me it is working one way from Mac to iPad.

Upvotes: 5

ThE uSeFuL
ThE uSeFuL

Reputation: 1534

If you are dealing with files in addition to core data. Here is a better tutorial

http://samvermette.com/312

Upvotes: 0

user1263865
user1263865

Reputation: 131

I think NSMetadataQueryDidUpdateNotification is what you are looking for to detect document update.

This can be used just like NSMetadataQueryDidFinishGatheringNotification.

Upvotes: 2

Related Questions