1. Jens Nerup
  2. ProsaWorkshop

Wiki

Clone wiki

ProsaWorkshop / Part-Two

Workshop - Part Two

Wireframe

Add a sub-controller to the details view (using MapKit)

Create a details view by adding a new controller and customize the view to show the data contained within a task. You'll also have to add a MKMapView which must be used to show the GPS coordinates. Remember to add the MapKit framework to your project. Next you'll have to bind the "parent" view controller to this details controller by using a push segue. Remember to set a proper "zoom"-level - try with something like .001f. You'll also have to add an annotation to be able to show a pin where the task was captured. Therefore make a class and implement the MKAnnotation protocol.

Add support for CoreData (frameworks and delegate)

Before you begin its recommended to read the "Introduction to Core Data Programming Guide" to get hold of CoreData.

Add a model to your project and create an entity named Task. Name the generated class PROManagedTask. Add all the properties from the JSON response to the newly created entity and then try to generate the managed class (or even better generate the class using mogenerator).

You ain't able to compile yet because something is missing - which is the CoreData framework. Added the framework and try compiling again. Next add the NSManagedObjectContext, NSManagedObjectModel and NSPersistentStoreCoordinator to your AppDelegate header file:

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

and implementation:

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

....

#pragma mark - Core Data stack

// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

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

// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel)
        return _managedObjectModel;

    NSString *path = [[NSBundle mainBundle] pathForResource:@"Model" ofType:@"momd"];
    if (!path)
    {
        path = [[NSBundle mainBundle] pathForResource:@"Model" ofType:@"mom"];
    }
    NSURL *modelURL = [NSURL fileURLWithPath:path];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Model.sqlite"];

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _persistentStoreCoordinator;
}

Next add the NSManagedObjectContext to the primary application viewcontroller (the tableview showing all the tasks). Added the following property to the view controller:

@property(nonatomic, strong) NSManagedObjectContext *managedObjectContext;

Finish by passing the NSManagedObjectContext to the primary application viewcontroller. This must be done in the (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions method in your AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
....
    //
    UINavigationController *navigationController = (UINavigationController *)[[self window] rootViewController];
    PROTaskTableViewController *rootApplicationViewController = (PROTaskTableViewController *)navigationController.topViewController;
    [rootApplicationViewController setManagedObjectContext: [self managedObjectContext]];
    return YES;
}

#pragma mark - Application's Documents directory

// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

Finish by passing the NSManagedObjectContext to the primary application viewcontroller (the tableview showing all the tasks). Added the following property to the tableview controller:

@property(nonatomic, strong) NSManagedObjectContext *managedObjectContext;

NB! TRY TO EXTRACT A PROTOCOL FROM YOUR PREVIOUS MODEL AND LET YOUR GENERATED CLASS IMPLEMENT THAT PROTOCOL

Add mogenerator tooling to your project

Start by installing the mogenerator tool. This can be done using home-brew by typing to following:

brew install mogenerator

when installed create an aggregate target in Xcode. Next "Add Build Phase" and select the option "Add Run Script". You can either choose to create a script to run mogenerator or you can type the command in the aggregate target. Which one you choose is up to you.

The following command line will command and arguments will create the generated objects in ${PROJECT_DIR}/Task/CoreData by reading your model located in ${PROJECT_DIR}/Task/CoreData/Model.xcdatamodeld/Model.xcdatamodel/ and use a template which supports ARC. Your script should as a minimum include the same:

/usr/local/bin/mogenerator -O ${PROJECT_DIR}/Task/CoreData -m ${PROJECT_DIR}/Task/CoreData/Model.xcdatamodeld/Model.xcdatamodel/ --template-var arc=true

Let your "primary" application target depend upon your newly created aggregate target. Added the generated classes to your project which can be found at ${PROJECT_DIR}/Task/CoreData. I recommended that you read the the "Getting Started with Mogenerator" blog post about mogenerator.

Create a new DataSource for the root UITableView

Now it's time to create a new datasource which uses a NSFetchedResultsController and change the new detail view to use NSManagedObjectContext to create the new objects. Create a new object and let it implement UITableViewDataSource and let it have an initializer method which takes a NSManagedObjectContext. Within the datasource make use of NSFetchedResultsController.

NB! TRY TO EXTRACT A PROTOCOL FROM YOUR PREVIOUS DATASOURCE AND LET YOUR COREDATA DATASOURCE IMPLEMENT THAT PROTOCOL

(Optional) Add a logging framework to your application

As an extra task try to add a logging framework to your application by using Cocoapods. Your could consider adding either NSLogger or CocoaLumberJack.

Updated