User Tools

Site Tools


ios-labs-s14:class-09

Drive Sense Final

This is the final tutorial section for the DriveSense project.

Please continue with the previous tutorial if you have not yet finished it. CoreData and Maps are important, and this section primarily focuses on Facebook.

In this section we will implement Facebook sharing, add a child view controller, and use NSNotifications to send alerts and data throughout the app without creating extra dependencies.


Facebook Sharing

Social sharing has become relatively easy with the past few releases of iOS. What formerly took hundreds of lines of code to accomplish can now be done in dozens thanks to strong frameworks and integration to social platforms. By downloading the Facebook framework and setting it up in your project you can issue quick and painless shares, likes, and posts.

Note: instructions copied over from Facebook's developer guide.

Downloading Facebook Framework

  1. Download the Facebook framework here. (Note: Facebook hosts an installable version, but the Mac lab computers will not allow you to install programs)
  2. Unzip the file
  3. Copy Facebook.framework into your Frameworks fold by dragging. Select “copy files” when prompted

You'll need a Facebook account to set up your project through the developer console.

Creating a Facebook App

  1. create a new app on the App Dashboard
  2. In the App Dashboard, select Apps > Create an app
  3. Fill in Display and Namespace with whatever you want

The bundle identifier is a string that uniquely identifies your app in the iOS app store. Each project gets an auto-generated bundle ID, which you can find by clicking the project file in Xcode under the “General” tab.

App Configuration

  1. In your app's page in the developer console, select “Settings” from the left-hand nav. Inside the settings, click on “Add Platform” and choose “iOS”
  2. Provide your Bundle Identifier in the “Bundle ID” field and enable “Single Sign On.” Save changes

There are a few configuration changes you must make to your project to allow the app to communicate with Facebook's servers securely. Facebook relies on OAuth to perform most of the authentication and authorization, which you can read more about here]].

Many of your app configuration details customized in the project file are stored as key/value pairs in a *.plist (property list) file. To set up Facebook, you'll have to edit this file directly, adding keys that Facebook creates.

Project Configuration

  1. Open *.plist file in the project navigator (inside of “Supporting Files” folder)
  2. Create a key called FacebookAppID with a string value, and add the app ID there.
  3. Create a key called FacebookDisplayName with a string value, and add the Display Name you configured in the App Dashboard.
  4. Create an array key called URL types with a single array sub-item called URL Schemes. Give this a single item with your app ID prefixed with fb. This is used to ensure the application will receive the callback URL of the web-based OAuth flow.

Setting up Facebook Call

  1. Create an IBAction for the settings button
  2. Copy the following methods into MapRoot
- (void)postToFacebook {
    // Put together the dialog parameters
     NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                   @"CS407 DriveSense Share", @"name",
                                   @"Sample Post.", @"caption",
                                   @"I just  learned how to set up Facebook Integration with iOS apps. If you want to do the same, you should take CS407 next semester!", @"description",
                                   @"http://pages.cs.wisc.edu/~suman/courses/wiki/doku.php?id=407-spring2014", @"link",
                                   @"http://static.freepik.com/free-photo/driving-sign-motorized-automobile-car-icon-symbol_121-99123.jpg", @"picture",
                                   nil];
    
    // Show the feed dialog
    [FBWebDialogs presentFeedDialogModallyWithSession:nil
                                           parameters:params
                                              handler:^(FBWebDialogResult result, NSURL *resultURL, NSError *error) {
                                                  if (error) {
                                                      // An error occurred, we need to handle the error
                                                      // See: https://developers.facebook.com/docs/ios/errors
                                                      NSLog([NSString stringWithFormat:@"Error publishing story: %@", error.description]);
                                                  } else {
                                                      if (result == FBWebDialogResultDialogNotCompleted) {
                                                          // User cancelled.
                                                          NSLog(@"User cancelled.");
                                                      } else {
                                                          // Handle the publish feed callback
                                                          NSDictionary *urlParams = [self parseURLParams:[resultURL query]];
                                                          
                                                          if (![urlParams valueForKey:@"post_id"]) {
                                                              // User cancelled.
                                                              NSLog(@"User cancelled.");
                                                              
                                                          } else {
                                                              // User clicked the Share button
                                                              NSString *result = [NSString stringWithFormat: @"Posted story, id: %@", [urlParams valueForKey:@"post_id"]];
                                                              NSLog(@"result %@", result);
                                                          }
                                                      }
                                                  }
                                              }];
}

// A function for parsing URL parameters returned by the Feed Dialog.
- (NSDictionary*)parseURLParams:(NSString *)query {
    NSArray *pairs = [query componentsSeparatedByString:@"&"];
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    for (NSString *pair in pairs) {
        NSArray *kv = [pair componentsSeparatedByString:@"="];
        NSString *val =
        [kv[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        params[kv[0]] = val;
    }
    return params;
}

What do these two methods do? The first creates and presents the share dialog with the given parameters, the second handles and callbacks from the dialog. Note that the first method is much longer and heavier than it needs to be, theres a lot of conditional result checking, as you can see from the NSLogs in the response block. You may not care about this functionality.

Thats all there is to it! If you want more information on Facebook sharing or iOS integration, check out the Facebook Developer pages.


NSNotification and Containers

Another extra credit section. This section is going to be difficult, the content is not going to be explicitly asked on the exam.

This section will demonstrate how to use NSNotification to provide app-wide updates to events. Here you'll use Containers to add a child view controller to MapRoot and implement the iPad version of the app.

When the user presses the Trips button, a table should animate onto the screen showing all the trips the user has recorded to the device. Because we want the same table to be available on the iPad version, but in a different location, the controller that manages the table needs to be completely dependent of MapRoot, hence NSNotifications.

Check out the building block page.

Creating a Child View Controller

  1. Add a container view to MapRoot in IB. Have it take up the bottom half of the screen
  2. Delete the stock controller included. Add a UITableViewController and set it as the embedded controller by right-clicking the container and dragging to the new controller
  3. Subclass the table view controller
  4. Subclass its cells
  5. Add labels to display Name and Date. Connect them as IBOutlets in the subclassed cell

Updating the Table

  1. Create a method called “updateTrips” in your subclassed table controller. Call getTrips in this method, updating an instance variable array with the contents and reloading the table
  2. Fill the contents of the cells with the relevant Trip properties
  3. Register this method with NSNotificationCenter using the examples methods provided in the NSNotification page linked above

Animating the Container In

  1. Copy and change the animation methods in MapRoot to perform the same animation for the container view
  2. Issue an NSNotification for the same key (used above in your table controller) in MapRoot when the Trips button is pressed

The whole reason for doing this is to make sure that the iPad version functions like the iPhone version while maintaining a different layout. In these next steps you'll quickly repeat the UI steps for the iPad version. The MapRoot will not have buttons, those buttons will instead reside in the iPad controller. We'll route the button callbacks to MapRoot by loading a reference to it when iPad controller loads. Note that embed segues can be referenced through identifiers just like any other seguews.

See the screenshot for the final iPad layout.

Building iPad Version

  1. Create a new view controller for the iPad version. Connect the containers as IBOutlets
  2. Delete the embedded controller for the left container. Ensure “clips subviews” is checked in IB.
  3. Have the larger container embed a MapRoot controller. Drag only a map onto this controller.
  4. Ensure the iPad storyboard is correctly referenced in the “Deployment Info” section of your project settings. Click the “iPad” button and make sure “Main Interface” references the correct name

Loading Controller from different Storyboard

  1. Copy following method into your iPad controller. Add instance variables of type MapRoot and UINavigation for mapVC and nav, respectively.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString: @"embed"]) {
        mapVC = (MapRootViewController *) [segue destinationViewController];
    }
    else if([segue.identifier isEqualToString: @"nav"]) {
        nav = (UINavigationController *) [segue destinationViewController];
    }
}
  1. Copy code into viewDidLoad:
  UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main_iPhone" bundle: nil];
    TripsViewController *table = [storyboard instantiateViewControllerWithIdentifier:@"table"];
    
    [self addChildViewController:table];
    [table didMoveToParentViewController:self];
    [containerLeft addSubview:table.view];

The last three lines in the last code chunk are the three canonical methods needed to add a child view controller programmatically. If you are nesting controllers, you must call them. Containers set up in IB do this automatically.

If you run the app now, you should see the embedded table take up way too much of the screen. Containers dont clip their embedded views by default; to make them cut off their children's views check “Clips Subviews” in IB.

Note the identifiers and embed strings above. Set these by selecting segues and controllers respectively and setting the “Identifier” properties in IB

Directly Calling IBAction to Fake Input

  1. Issue calls directly to the IBAction methods of MapRoot for the respective methods in iPadController. Pass nil as the only argument.
  2. Test functionality

Whew. If you made it past all that, congratulations– you've graduated the 407 labs with more than a firm grasp the basics of iOS development. Go make some killer apps.

Conclusion

Thanks for sticking through the labs. If you have any questions on projects, feel free to email your TA with questions or post on Piazza.

Good Luck!

ios-labs-s14/class-09.txt · Last modified: 2014/02/28 14:16 by mbarboi