ylliX - Online Advertising Network

Tutorial: Using CoreData with iCloud synchro

You can get tutorial source from https://github.com/blastar/CoreDataWithiCloud/tree/master

I saw lot of tutorials about this, but almost none were working.

So start with project as usual, for simplicity we can use Single View Application, in project capabilities enable iCloud:

enable icloud

Next open your pch file (in my project CoreDataWithiCloud-Prefix.pch) and add #import <CoreData/CoreData.h> so it will look like:

#import <Availability.h>

#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
    #import <CoreData/CoreData.h>
#endif

Open AppDelegate.h and add:

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSString *)applicationDocumentsDirectory;

below

@property (strong, nonatomic) UIWindow *window;

Open AppDelegate.m and add:

@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

below:

@implementation AppDelegate

then still in AppDelegate.m just before:

@end

add:

- (NSString *)applicationDocumentsDirectory {
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

- (NSURL *)grabCloudPath:(NSString *)filename {
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *teamID = @"XXXXXXXXX"; // replace with your real Team ID
    NSString *bundleID = [[NSBundle mainBundle]bundleIdentifier];
    NSString *cloudRoot = [NSString stringWithFormat:@"%@.%@", teamID, bundleID];
    
    NSURL *cloudRootURL = [fileManager URLForUbiquityContainerIdentifier:cloudRoot];
    
    NSString *pathToCloudFile = [[cloudRootURL path]stringByAppendingPathComponent:@"Documents"];
    pathToCloudFile = [pathToCloudFile stringByAppendingPathComponent:filename];
    
    return [NSURL fileURLWithPath:pathToCloudFile];
}

- (NSManagedObjectContext *) managedObjectContext {
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator: coordinator];
    }
    
    return _managedObjectContext;
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
                                               stringByAppendingPathComponent: @"database.sqlite"]];
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                   initWithManagedObjectModel:[self managedObjectModel]];
    if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                  configuration:nil URL:storeUrl options:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" } error:&error]) {
        /*Error for store creation should be handled in here*/
    }

    return _persistentStoreCoordinator;
}

Then go to your ViewController.h and add some stuff so this file will look like:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
{
    NSFileManager* fileManager;
    BOOL isSignedIntoICloud;
    NSArray* items;
}

@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;

@end

now its time for ViewController.m, import your AppDelegate by adding #import “AppDelegate.h” at top, then add:

items = [[NSArray alloc] init];
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
self.managedObjectContext = appDelegate.managedObjectContext;

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(contentDidChange:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:self.managedObjectContext.persistentStoreCoordinator];

[self createEntry];

in viewDidLoad, and below this method add new ones:

- (void)createEntry {
    NSDateFormatter *timeFormat = [[NSDateFormatter alloc] init];
    [timeFormat setDateFormat:@"HH:mm:ss"];
    NSString *theTime = [timeFormat stringFromDate:[[NSDate alloc] init]];
    
    Contacts* entry = [NSEntityDescription insertNewObjectForEntityForName:@"Contacts"
                                                    inManagedObjectContext:self.managedObjectContext];
    entry.name = theTime;
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
    }
    
}

-(NSArray*)getAllRecords
{
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Contacts"
                                              inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    NSError* error;
    
    NSArray *fetchedRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
    
    return fetchedRecords;
}

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

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    NSDictionary *changes = notification.userInfo;
    NSMutableSet *allChanges = [NSMutableSet new];
    [allChanges unionSet:changes[NSInsertedObjectsKey]];
    [allChanges unionSet:changes[NSUpdatedObjectsKey]];
    [allChanges unionSet:changes[NSDeletedObjectsKey]];
    
    for (NSManagedObjectID *objID in allChanges) {
        Contacts *item = (Contacts *)[self.managedObjectContext objectWithID:objID];
        NSLog(@"item = %@", item.name);
    }
    items = [self getAllRecords];
}

As you can see at this point, XCode will mark Contacts as error, as we don’t have any entities yet. So its time to add some. Add new file to your project, and choose Core Data/Data Model:

Data Model

You can call it whatever you want, I called it “MyModel” so after saving you should see MyModel.xcdatamodeld file. Click it and you should see empty entities panel:

Entities

Now its time do add our entity, at top menu click Editor/Add entity (you can use button at bottom of panel) and new entity will be added. Rename it to “Contacts” and add new property. Call it “name” and change type to “String”, now your view should look like this:

Entity

Now we need to create out class, make sure you have your entity highlighted on left pane, then in top menu choose File/New/File and choose NSManagedObject subclass:

subclass

On next screen make sure you have MyModel checked:

Entity

Click “next” and check your entity:

Entity

Then you will be prompted to choose folder, after that you should see Contacts.h and Contacts.m in your project. Now add #import “Contacts.h” to your ViewController.m file and XCode errors should be gone. Now at last you can compile and run on your device, you should see blank screen (as we did not added any controls) and in debug area something like this:

2014-07-02 20:58:27.667 CoreDataWithiCloud[1357:1803] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](771): CoreData: Ubiquity:  mobile~09896823-D161-4CD2-92A0-F8E42EAFAB9D:iCloudStore
Using local storage: 0

Now its time to test whole thing, stop app on first device, and run it on second one, output will be similiar as above. Then the most important part – on first device run you app again, then wait ca. 30s and on second device (still running via XCode – so you should see your debug area) you should see:

2014-07-02 21:02:35.959 CoreDataWithiCloud[2030:420b] item = 21:02:24

If you see such line, congratulations, you did your first iCloud-aware app.

But what just happened? On your second device you had your app running, and listening for NSPersistentStoreDidImportUbiquitousContentChangesNotification notification, when it comes, – (void)contentDidChange:(NSNotification *)notification method is called, in which first you merged all changes, and then they are displayed in debug. Since we only adding items iterating on changes[NSInsertedObjectsKey] will be enough, but I added all variants.

You can get tutorial source from https://github.com/blastar/CoreDataWithiCloud/tree/master

Leave a Reply