Seamless Parse, Node, and iOS integration - Part two

Parse is an SaaS for developers with an intuitive API and clearly-written tutorials. Resource and time-starved start-ups and indie devs looking for a web-to-mobile solution should consider it.

In the first part of this tutorial, you created a basic web app with Node.js using the Node-Parse-API to persist User and file objects to the Parse data browser. Now you will retrieve that data on an iOS device with The Parse iOS framework.
This tutorial assumes a working knowledge of Node.js and Objective-C and that you have installed Xcode on your Mac. Before we begin, make sure that you have gone through the first part of the tutorial so that User and file objects exist on the Parse cloud for you to pull onto your device. You can clone the demo's created in this tutorial here:
The iOS app
Download the blank starter project from Parse which has all the dependencies linked and ready to go.
Subclass UIViewController and name it LoginViewController. Check the xib box before saving.
In ParseStarterProjectAppDelegate.h, import LoginViewController and assure clang that ParseStarterProjectViewController exists

    #import "LoginViewController.h"
    @class ParseStarterProjectViewController;

    @interface ParseStarterProjectAppDelegate : NSObject 

    @property (nonatomic, strong) IBOutlet UIWindow *window;
    @property (nonatomic, strong) IBOutlet ParseStarterProjectViewController *viewController;

    @end

In ParseStarterProjectAppDelegate.m, in application:didFinishLaunchingWithOptions, uncomment line 13 and add the same application ID and client key from part one of this tutorial.
Now replace lines 20-31 with the following:

if ([PFUser currentUser])
{
    self.window.rootViewController = self.viewController;
}
else
{
    self.window.rootViewController = self.loginViewController;
}

Next, query Parse for a current user. If a User object comes back, the viewController provided with the sample app is loaded, if not, we set the rootViewController to the loginViewController object so that the user can login. Your application:didFinishLaunchingWithOptions method should now look like:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // ****************************************************************************
    // Uncomment and fill in with your Parse credentials:
    [Parse setApplicationId:@"" clientKey:@""];
    //
    // If you are using Facebook, uncomment and add your FacebookAppID to your bundle's plist as
    // described here: https://developers.facebook.com/docs/getting-started/facebook-sdk-for-ios/
    // [PFFacebookUtils initializeFacebook];
    // ****************************************************************************
    
    LoginViewController *loginView = [[LoginViewController alloc] initWithNibName:@"LoginViewController" bundle:nil];
    
    if ([PFUser currentUser])
    {
    self.window.rootViewController = self.viewController;
    }
    else
    {
    self.window.rootViewController = loginView;
    }
    [self.window makeKeyAndVisible];

    [PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
    [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge|
    UIRemoteNotificationTypeAlert| UIRemoteNotificationTypeSound];
    return YES;  
}

Instantiate a LoginViewController object that you'll display if the call to currentUser returns false. If the call returns true, display the viewController object that was provided with the starter project.
Moving to LoginViewController.m, add two IBOutlet instance variables in the @interface declaration:

@interface LoginViewController ()
    {
        IBOutlet UITextField *userNameField;
        IBOutlet UITextField *passwordField;
    }

...

In this same file, you need the following processFieldEntries method to handle user input:

- (IBAction)processFieldEntries:(id)sender{
    NSString *username = [userNameField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    NSString *password = [passwordField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    
    [PFUser logInWithUsernameInBackground:username password:password block:^(PFUser *user, NSError *error) {

if (user) 
{
    ParseStarterProjectViewController *viewController = [[ParseStarterProjectViewController alloc] initWithNibName:@"ParseStarterProjectViewController" bundle:nil];
    [self presentViewController:viewController animated:YES completion:nil];
} 
else 
{

    // Bring up keyboard, so the user can try again
    [userNameField becomeFirstResponder];
    }
  }];
}

  • Do some basic client-side validations to remove any whitespace. Obviously you should include more validations before submitting to the App Store.
  • call logInWithUsernameInBackground on PFUser passing it the username, the password, and a block so that responses are only processed as they come in. If we get a User object, then our viewController is shown, otherwise, the error is logged.
  • Create a login interface in LoginViewController.xib by dragging two UITextField objects and a UIButton into the xib. In the attributes inspector, for the first and second UITextFields, find the placeholder property and enter username and password for the top and bottom textfields respectively. Change the UIButton's title to login.
  • Finally, in the connections inspector, wire up the text fields to their instance variables and connect processFieldEntries to the UIButton's Touch Up Inside Sent Event.
Your xib is lookin' good. Minimal enough to win an Apple Design Award:

Want more details on the above? Check out Parse's AnyWall tutorial (Parse's tutorials inspire me to write better tutorials).
Test the app by running it in the simulator. Make sure to login with the same username and password that you created in part one.
After tapping Login, you should see the ParseStarterProjectViewController view, so modify that next.
First, change the 'You're all set with Parse' label to read 'This text should change upon import' in the xib file. On this label, we will pull the text that you pushed to the Parse data browser with Node.js in part one. You may want to make the label much larger to accommodate the text you pushed earlier.
Change the button title property to read 'Import'. It's the little things, guys.

In the implementation file for this class, you also need to add one method:

- (IBAction)importFromParse:(id)sender
  {
    PFUser *user = [PFUser currentUser];
    NSString *userID = user.objectId;
    PFQuery *query = [PFQuery queryWithClassName:@"Terms"];
    [query whereKey:@"link"
    containsString: userID];
    [query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
    if (!object)
    {
    fileLabel.text = @"Either your device is offline, or you have not uploaded any terms from the webservice.  Please refer to the first part of the tutorial at leveton.blogspot.com/2012/09/connecting-nodejs-to-ios-with-parse.html";
  }
else
  {
    NSObject *link_address = [object valueForKey:@"link"];
    NSURL *url = [NSURL URLWithString: (NSString *)link_address];
    NSData *urlData = [NSData dataWithContentsOfURL:url];
    NSString *datastring = [[NSString alloc] initWithData:urlData encoding:NSUTF8StringEncoding];
    fileLabel.text = datastring;

  }
 }];
}

In importFromParse:
  • Get the current user from the PFUser class.
  • Set the User object's objectId property to a string.
  • Use Parse's semi-magical PFQuery class to narrow our search to the link column of the Terms class in the date browser.
  • This link key can return multiple values if it finds the current user's objectId more that once, so use getFirstObjectInBackgroundWithBlock to show only the first hit. Remember, current user's objectId is automatically generated by the Parse User class. Here's the table you're querying from:

The famous Parse data browser

  • Assuming you have an object, get the value of the link row and set it to an NSURL.
  • Finally set url to an NSData object, deserialize it to a string, and set it as our label's text property.
You should now see the content of the text file that you created before on the simulator. ¡Pagata!
Copyright © 2011–2015 Mike Leveton