oAuth2 with iOS (part 1)

So, now we are finished with the Android app, we start with creating the iOS app. For previous posts in this series, see here :).
Currently, Apple announced iOS 8 with their new swift language and a new xCode on WWDC a few days ago. While I am not going to use Swift for this tutorial, I will use Xcode 6 and iOS 8. I haven’t tried any of this code on previous iOS/xcode versions, however I guess it should not be a issue.

Lets start with the basics. We will use the same approach as with the Android app. We have a local Database (In this case we use core data, more on that later on), a simple table view, and a few buttons to add or delete data.
To implement the Core Data stuff, I use some code from tutorials created by Paul Hegarty. I won’t be going very deep into the Core Data related stuff, it is explained very well by Paul.

All code and examples I provide here are tested/shown on a iPad mini retina or a iPhone 5S. Both have iOS 8 Beta 1.

Like with he Android app, all code can be found in GIT. The code like at the end of the project can be found here.

So, first, lets start by creating a new empty  project. In Xcode, choose for Empty Project, after that, fill in the form like on the left.Create projectNow, save it somewhere you like yourself. (While it is in the screenshot ticket, please don’t select Use Core data!)

Now we have the project, we are first going to start with creating our Entities, and Core Data related stuff.
Make a new “data model” file via file -> new -> Core Data. Give it a nice name, I choose Model :).
Create within the object a new entity called Demo, with the fields serverId, title and desc (You can’t use description!), like on the image left.create entityNow we have our entity. This was a lot easier as in android, wasn’t it? :).
Now lets create a nice class for our entity. This is so we won’t get a ID back from core data, but a real class.
First select the Demo entity in our Model. Then go to Editor ->Create NSManagedObject subclass. Now just follow the wizard, and two new files will be created, Demo.m and Demo.h. Because this is a very simple entity, there is not much complexity at all in these two files.

Before we go into doing some stuff with it, we are first going to create a bit of our UI. This is done in Main.storyboard. There is already a view controller here, but we don’t want that, so just remove it.
Add a new tableviewcontroller by dragging it from the right, to the middle. We want to do some navigation later on, so go to editor -> embed -> embed in Navigation  Controller. A new controller will be added by xCode in front of our tableviewcontroller. It should now looks like in the image left.initial view controllers Now lets focus on the tableviewcontroller. We want a button to add a new item. find at the bottom right the Bar button item, and drag it into the top right corner. It will snap into place. You just added a new button :)! Lets make the button a add button. At the top right, select the fourth icon. Here we can modify some properties for this button. Lets select Identifier, and set it to add. The button now changed to a + sign. Nice :).

If you run the app in its current state, it  will just work. You will see a empty table, and a button at the right. When you tap the button, it won’t do anything. So lets create a new view, that will be shown when tabbing that button.
Drag from the bottom right a new view controller into the middle. Now, ctrl drag from the button, to the new view controller. This creates a so called “Segue”.  To keep things easy, we choose for show (e.g. Push). You can also use “Present as Popover”, however you will need to do some more initialisation for that. Because we are in a view controller, the is the easiest way of doing it :). We also want to give it a nice name, so lets set the identifier to “create-new-demo”.
Now lets create some fields in that view. Just drag a two labels for title and description, and two textfields for them into their. Line them up nicely on the blue line. Also add a button to save it.
Thats the views for now. It should looks like the image left.add view
Now we have our basic User Interface, lets start with creating the backend for the app. Like I said before, I am going to use some basic classes from Paul Hegarty. This makes implementing Core Data with a table view controller very easy. Download the zip CoreDataTableViewController from here.  This zip contains two files. Drag these two files into Xcode.
This class will do all required handling for the Core Data. If we insert something new into the database, it will automatically update the table if required. The only thing we need to do is implement the next method:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

We also need to set the fetchedResultsController from the class we inherit from.
So lets start with creating a new file. Choose for a Cocoa touch class. Lets call it DemoTableViewController and make it a subclass of CoreDataTableViewController.
Now, before we create the required methods, we need to make sure that our cell has a Identifier, because we are going to use that in our view controller. Besides that, now we have our ViewController, we can also assign that to the tableviewcontroller.
To set the identifier for the cell, you select the cell, and the at the top right at the fourth icon, the second item is Identifier. Lets call it demo-cell. We also set the style (First option) to subtitle for the cell.
To set the view controller select the Table View Controller at the left. At the top right the first option is class. Here you set the class to DemoTableViewController.
Now lets do some implementations for the required method.
First, we need to add a property to the interface in DemoTableViewController.h:

@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;

This property is required for the fetch controller to do his work :).
In AwakeFromNib we are going to listen to a observer that sends the managedContext:

- (void)awakeFromNib
{
    [[NSNotificationCenter defaultCenter] addObserverForName:DatabaseAvailabilityNotification
                                                      object:nil
                                                       queue:nil
                                                  usingBlock:^(NSNotification *note) {
                                                      self.managedObjectContext = note.userInfo[DatabaseAvailabilityContext];
                                                  }];
}

These two constants (DatabaseAvailabilityNotification and DatabaseAvailabilityContext) are defined in a new file named DatabaseRadio.h:

//
//  DatabaseRadio.h
//  Symfony iOS Oauth Demo
//
//  Created by Paul Sohier on 09-06-14.
//  Copyright (c) 2014 Paul Sohier. All rights reserved.
//

#ifndef DatabaseAvail_h
#define DatabaseAvail_h

#define DatabaseAvailabilityNotification @"DatabaseAvailabilityNotification"
#define DatabaseAvailabilityContext @"Context"

#endif

Now, when we receive a managedObject, we can create our fetch controller. The easiest way of doing this is in the setter for the managedObject:

- (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
    _managedObjectContext = managedObjectContext;
    
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Demo"];
    request.predicate = nil;
    request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"title"
                                                              ascending:YES
                                                               selector:@selector(localizedStandardCompare:)]];
    
    
    
    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                        managedObjectContext:managedObjectContext
                                                                          sectionNameKeyPath:nil
                                                                                   cacheName:nil];
}

Now we just need to set the layout for the cell:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"demo-cell"];
    
    Demo *demo = [self.fetchedResultsController objectAtIndexPath:indexPath];
    
    cell.textLabel.text = demo.title;
    cell.detailTextLabel.text = demo.desc;
    
    return cell;
}

The view controller should be done now. If you run the app, however, there won’t happen anything. Why? Because we don’t have a managed object context yet. So lets make that happen.

To get the context we are going to create a new category on our delegate. For some reason, there is no longer a option in Xcode to directly create a category, so we need to do it by hand. First, we create a AppDelegate+radio.h with the following code:

//
//  AppDelegate+radio.h
//  Symfony iOS Oauth Demo
//
//  Created by Paul Sohier on 09-06-14.
//  Copyright (c) 2014 Paul Sohier. All rights reserved.
//

#import "AppDelegate.h"

@interface AppDelegate (radio)

- (NSManagedObjectContext *)createMainQueueManagedObjectContext;

@end

Now we create a AppDelegate+radio.m:

//
//  AppDelagate+radio.m
//  Demo
//
//  This code comes from the Xcode template for Master-Detail application.

#import "AppDelegate+radio.h"
#import 

@implementation AppDelegate (radio)

#pragma mark - Core Data

- (void)saveContext:(NSManagedObjectContext *)managedObjectContext
{
    NSError *error = nil;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

// 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 *)createMainQueueManagedObjectContext
{
    NSManagedObjectContext *managedObjectContext = nil;
    NSPersistentStoreCoordinator *coordinator = [self createPersistentStoreCoordinator];
    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 *)createManagedObjectModel
{
    NSLog(@"Creating managedObjectModel");
    NSManagedObjectModel *managedObjectModel = nil;
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
    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 *)createPersistentStoreCoordinator
{
    NSPersistentStoreCoordinator *persistentStoreCoordinator = nil;
    NSManagedObjectModel *managedObjectModel = [self createManagedObjectModel];
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Demo.sqlite"];
    
    NSError *error = nil;
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.
         
         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
         
         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible with current managed object model.
         Check the error message to determine what the actual problem was.
         
         
         If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
         
         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
         
         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
         @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
         
         Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
         
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return persistentStoreCoordinator;
}

// Returns the URL to the application's Documents directory

- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

@end

This code is copied from Master+Detail application.

Now, we need to use the category we just created.
In our appDelegate.m we add to the interface a property:

@property (strong, nonatomic) NSManagedObjectContext *DatabaseContext;

And in the didFinishLaunchingWithOptions we set it:

self.DatabaseContext = [self createMainQueueManagedObjectContext];

This won’t do much, as we need to create a setter for the DatabaseContext:

// we do some stuff when our Demo database's context becomes available
// we post a notification to let others know the context is available
- (void)setDatabaseContext:(NSManagedObjectContext *)DatabaseContext
{
    _DatabaseContext = DatabaseContext;
    
    // let everyone who might be interested know this context is available
    // this happens very early in the running of our application
    // it would make NO SENSE to listen to this radio station in a View Controller that was segued to, for example
    // (but that's okay because a segued-to View Controller would presumably be "prepared" by being given a context to work in)
    NSDictionary *userInfo = self.DatabaseContext ? @{ DatabaseAvailabilityContext : self.DatabaseContext } : nil;
    [[NSNotificationCenter defaultCenter] postNotificationName:DatabaseAvailabilityNotification
                                                        object:self
                                                      userInfo:userInfo];
}

In a later stadium, we will fetch the Demo’s from the server here as well.
So now lets run the app again :). Badly enough, because there is no data yet in our Model, we won’t see anything (Again :(). But we can see if our code works. The TableViewController from Paul has a Debug option. If you set that to yet in your view controller, you will see some debug output, like this:

2014-06-09 13:12:56.844 Symfony iOS Oauth Demo[1195:692329] Creating managedObjectModel
2014-06-09 13:12:56.915 Symfony iOS Oauth Demo[1195:692329] [DemoTableViewController setFetchedResultsController:] set
2014-06-09 13:12:56.916 Symfony iOS Oauth Demo[1195:692329] [DemoTableViewController performFetch] fetching all Demo (i.e., no predicate)

When you see the last two lines, it means the managedObject was created correctly, however, because nothing was added yet to the Core Data database, nothing is shown. If you did everything right, it should look like the image on the left on the iPad and iPhone.iOS demo app
demo iPhone
In the next part of this tutorial, we will start by filling the database with some data, so we can test everything.

Leave a Reply

Your email address will not be published. Required fields are marked *