CPSC 91 Spring 2011: Lab 05


Introduction

In this week's lab, you will implement an iPad version of Lab 4.

Like last week's lab, your code:

Prerequisites

Before beginning, your Lab 04 needs to be working. If you haven't got that working yet, you'll need to finish that before you can begin this. Ask me for help so I can get you ready for Lab 05.

There is no need to make a new repository for Lab 05 since you are simply extending the functionality of Lab 04.

You should refer to the StudentManager-iPad project we built in class for hints on how to complete this lab.

Overview

  1. Your app should implement a UISplitViewController if you're on the iPad and should implement a UINavigationController if you're on the iPhone/iPod. You're on an iPad if the following is true: UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
  2. Your app should support a UISwipeGestureRecognizer that allows you to swipe both to the left and to the right across the displayed cards. Swiping should bring you to the next card in the selected set. In the UISplitViewController, swiping should also update the selected row in the table view. (It is easier to just update it in both, but you won't see it happen in the UINavigationController version since the table view won't be on screen when you're doing your swiping.)
  3. Your app should save the card set that the user is interested in view using NSUserDefaults.

UISplitViewController

When you are on an iPad, use a UISplitViewController to display the cards list and their images. The table view controller should be the master view controller, and the card image view controller should be the detail view controller.

Important note: You need to convert your app to work on the iPad. Select "Targets" from the left column menu (it looks like a red bulls eye). Click on your app name (for example, RFTG) and then choose Project->Upgrade Current Target for iPad. Be sure to select "Universal Application".

When you are in Portrait mode, your app should display a button on the right side that pulls down the hidden table view. When you are in Landscape mode, your button should go away.

When the app first loads, there is no image set to be displayed in your detail view. Whenever the image view controller determines that it doesn't know what image to display, display the "cardback.jpg" image. For example, use that image when the app loads. You may also want to use that image when the card set changes, or maybe only when the card set changes and the card you had been viewing is not in the new set.

Note that your app should also support rotation to both portrait and landscape mode in order to fully test the UISplitView. You'll notice that the image size is incorrect when you rotate, so you should use this line in your -loadView method of your image view controller (after you've set up the view):

self.view.contentMode = UIViewContentModeScaleAspectFit;

UISwipeGestureRecognizer

The Student Manager app contains an example of using a swipe gesture recognizer. You will want to implement something quite similar. When a card image is swiped, you will to show the next (or previous) card in the current set your are viewing.

In -viewDidLoad of your image view controller, you are going to add to swipe gesture recognizers. A swipe gesture recognizer can only detect swipes in one direction. Since you'd like to find left and right swipes, you need to create two recognizers. If you don't specify the directionality of the swipe, it is to the right by default. Below, I am assuming that your view is called imageView

UISwipeGestureRecognizer *swipegr;

// right swipe
swipegr = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                    action:@selector(swipe:)];
[imageView addGestureRecognizer:swipegr];
[swipegr release]; //imageView has now retained this         

// left swipe
swipegr = [[UISwipeGestureRecognizer alloc] initWithTarget:self 
                                                    action:@selector(swipe:)];
swipegr.direction = UISwipeGestureRecognizerDirectionLeft;
[imageView addGestureRecognizer:swipegr];
[swipegr release]; //imageView has now retained this         

Notice that both gesture recognizers will call the swipe: method when they detect a swipe, so we need to write that method. It will look something like this:

- (void)swipe:(UISwipeGestureRecognizer*)recognizer
{
  if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
    //do something for left
  } else if (recognizer.direction == UISwipeGestureRecognizerDirectionRight) {
    //do something for right
  } else {
    //ignore up and down swipes
  }
}

The key here is that when you swipe, you're going to need to tell the table view controller that you swiped. You need to tell the table view controller for two reasons:

  1. The table view controller can tell you what card comes next.
  2. The table view controller can update the table view to show the correctly selected card.

This means you are going to need a delegate. You probably had a delegate from Lab 04, but if you didn't, you need one now. (Including all the steps required: declaring the protocol, adding an instance variable, setting up the property, synthesizing the property, telling the table view controller it should adhere to the protocol, implementing the method in the table view controller, and assigning the delegate to the table view controller.) Assuming you had one from Lab 04, you can simply add a new method to your protocol and have the table view controller implement that for you.

NSUserDefaults

NSUserDefaults gives you a persistent store for small amounts of information, usually related to some default configurations for your app. You should read the documentation for the complete API. Here are some examples:

//Read an integer with key "filterSet" out of NSUserDefaults
//An integer key that doesn't exist returns 0. 
int filter = [[NSUserDefaults standardUserDefaults] integerForKey:@"filterSet"];

//Set a value in NSUserDefaults -- does not write to disk
[[NSUserDefaults standardUserDefaults] setInteger:filter
                                           forKey:@"filterSet"];

//Write the NSUserDefaults to disk
[[NSUserDefaults standardUserDefaults] synchronize];

Think about when you need to read the defaults from disk (at the start of the app), when you need to change the defaults (when someone changes the segmented index), and when you'd like to write to disk. Since the data you are writing to disk is so small, you could just write every time the defaults change. Or you could write only when the app is about to terminate or go into the background.

Optional

Assuming you have a alternate data set that you are interested in, you are welcome to use any other data set to complete this lab. In that case, you must create a new repository for lab 05. You app must run on the iPod/iPad, using a navigation controller on the iPod and a split view controller on the iPad. You must have a table with sections as the master controller. The table should have non-trivial numbers of sections and rows. Your detail controller does not have to show an image, but it should support swiping to get to the next and previous entries in the table shown in the master controller. You should have some configurable options that you can write out/read back from NSUserDefaults.