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.
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.
You'll need a Facebook account to set up your project through the developer console.
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.
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.
- (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.
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.
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.
- (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]; } }
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
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.
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!