Chapter 13
Controls—Part Two: Using Pickers and Using the Camera

Key Skills & Concepts

• Using a UIDatePicker to select dates

• Using a UIPickerView to select values

• Using a UIPickerView with multiple components

• Using a UIPickerView with UIImageView

• Using the UIImagePickerController to control the camera and access the photo library

• Using simple NSNotifications

What do the UIPickerView and the UIImagePicker classes have in common? Nothing, really, other than they are both ultimately UIViews. But they are both controls you might use in an iOS application, and so they are covered together in this second chapter on iOS controls. This chapter’s first half covers using the UIDatePicker and UIPickerView classes. These classes create visual controls that appear similar to Las Vegas slot machines. This chapter’s second half covers using the UIImagePickerController. This class gives you a way to programmatically access the photo library on any iOS device and the camera roll and camera on an iPhone or new iPod Touch that includes a camera.

Using Pickers: Date Pickers and Pickers

You use pickers to select one of several values. There are two types of pickers: date pickers and pickers. As you would expect, date pickers are used for selecting a date, time, or countdown interval. Pickers are used for selecting one of many values.

Date Pickers

Date pickers pick dates and times, placing the selected values in an NSDate class, and are implemented using the UIDatePicker class. If using Interface Builder to develop an iOS application, you drag a date picker onto your view’s canvas from the library and modify the picker’s properties in the Inspector. You then create an IBOutlet for the picker in your view’s associated view controller and connect it to the picker on your view’s canvas. The view controller uses this outlet to obtain the UIDatePicker object’s selected date. The example later in this section illustrates this process.

UIDatePicker

A UIDatePicker has rotating wheels, allowing a user to select the date and time. If using a date picker to pick a date and time, valid modes are Date & Time, Time, and Date. If using a date picker to pick a countdown time, the valid mode is Timer. Figure 13-1 illustrates the picker’s visual appearance for the different modes.

Image

Figure 13-1 UIDatePicker has four modes: Date & Time, Time, Date, and Timer.

There are several attributes you might change from the default when using a UIDatePicker. Figure 13-2 illustrates the mode, locale, timeZone, date, minimumDate, maximumDate, and interval properties, as viewed in the Inspector.

The locale is an NSLocale. This class encapsulates how the date should be formatted for a different date or culture. The timeZone is an NSTimeZone; its default value is the operating system’s time zone. The minuteInterval is the minute intervals displayed by the picker. For instance, Figure 13-3 illustrates a UIDatePicker using 30-minute intervals and a UIDatePicker using 1-minute intervals.

NSDate

UIDatePickers return values as an NSDate. You access a date picker’s value through its date property. For instance, the following sets an NSDate from a UIDatePicker’s date property.

NSDate * theDate = self.datePicker.date;

Image

Figure 13-2 Several UIDatePicker properties you might change in the Inspector

Image

Figure 13-3 A UIDatePicker showing 30-minute intervals and a UIDatePicker showing 1-minute intervals

An NSDate is a date and time. Several NSDate methods you might use include isEqualToDate:, earlierDate:, compare:, and laterDate:. For a more complete understanding of date and time programming, refer to Apple’s “Date and Time Programming Guide for Cocoa,” available online.

NSDateFormatter

When you wish to display a date, you use an NSDateFormatter. An NSDateFormatter allows a date to be displayed as a formatted NSString. There are many formats you might use. For instance, consider the two formatters.

NSDateFormatter * dateFormatter =
     [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateFormat:@"MM/dd/yyyy"];

The “MM/dd/yyyy” format outputs August 26, 2008, as “08/26/2008.”

[dateFormatter setDateFormat:@"EEEE MMMM d',' yyyy"];

The “EEEE MMMM d’,’ yyyy” format outputs “Tuesday August 26, 2008,” as the date. These are only two of many choices you might select to format dates and times. For more formats, refer to Apple’s “Data Formatting Programming Guide for Cocoa,” available online.

Try This
Using a Date Picker

1. Create a new View-based Application. Name the application DatePickerProject.

2. Open DatePickerProjectViewController.xib in Interface Builder.

3. Add a date picker to the canvas and add a label (Figure 13-4).

4. Change the date picker’s mode to Date (Figure 13-5).

5. Save your changes.

6. Open DatePickerProjectViewController and add an IBOutlet for the UIDatePicker and an IBOutlet for the UILabel (Listings 13-1 and 13-2). Also add an IBAction called changeValue. Don’t forget to release the two outlets in the dealloc method.

Listing 13-1 DatePickerProjectViewController.h

#import <UIKit/UIKit.h>
@interface DatePickerProjectViewController : UIViewController {
 UIDatePicker * datePicker;
 UILabel * theLabel;
}

Image

Figure 13-4 Adding a UIDatePicker and UILabel to the view

Image

Figure 13-5 Change a UIDatePicker’s mode to Date

@property (nonatomic, retain) IBOutlet UIDatePicker * datePicker;
@property (nonatomic, retain) IBOutlet UILabel * theLabel;
- (IBAction) changeValue: (id) sender;
@end

Listing 13-2 DatePickerProjectViewController.m

#import "DatePickerProjectViewController.h"
@implementation DatePickerProjectViewController
@synthesize datePicker;
@synthesize theLabel;
- (IBAction) changeValue: (id) sender {
  NSDate * theDate = self.datePicker.date;
  NSLog(@"the date picked is: %@", [theDate description]);
  NSDateFormatter * dateFormatter = [[[NSDateFormatter alloc] init]
autorelease];
  [dateFormatter setDateFormat:@"MM/dd/yyyy"];
  NSLog(@"formatted: %@", [dateFormatter stringFromDate:theDate]);
  [dateFormatter setDateFormat:@"EEEE MMMM d',' yyyy"];
  NSLog(@"formatted: %@", [dateFormatter stringFromDate:theDate]);
  [theLabel setText:[theDate description]];
 }
 - (void)dealloc
{
  [datePicker release];
  [theLabel release];
  [super dealloc];
}
@end

7. Save your changes and open DatePickerProjectViewController.xib in Interface Builder.

8. Connect the File’s Owner datePicker outlet to the UIDatePicker on the canvas. Connect the File’s Owner theLabel to the UILabel on the canvas.

9. Connect the File’s Owner changeValue action to the UIDatePicker’s Value Changed event (Figure 13-6).

10. Save your changes and click Run to run the application in iPhone Simulator (Figure 13-7). The Debugger Console should have logging similar to Listing 13-3.

Listing 13-3 Debugger Console output

2010-09-10 11:38:36.445 DatePickerProject[7095:207] the date picked is:
2010-09-11 17:25:54 GMT

Image

Figure 13-6 Connecting to the UIDatePicker’s value changed event

Image

Figure 13-7 The application running in iPhone Simulator

2010-09-10 11:38:36.455 DatePickerProject[7095:207] formatted: 09/11/2010
2010-09-10 11:38:36.455 DatePickerProject[7095:207] formatted: Saturday
September 11, 2010
2010-09-10 11:38:58.838 DatePickerProject[7095:207] the date picked is:
2010-10-11 17:25:54 GMT
2010-09-10 11:38:58.840 DatePickerProject[7095:207] formatted: 10/11/2010
2010-09-10 11:38:58.841 DatePickerProject[7095:207] formatted: Monday
October 11, 2010

Try This
Using a UIDatePicker in Timer Mode

UIDatePicker classes are also useful for selecting a duration for an NSTimer. A UIDatePicker selects an NSDate, which consists of a date and a time; therefore, you can create a timer using an NSDate’s time. But a UIDatePicker does not implement a timer; it only provides a visual way to pick duration. You must then use this duration with an NSTimer. This task illustrates using a UIDatePicker to select a duration for an NSTimer and, in the process, illustrates using the NSCalendar, NSDateFormatter, and NSDateComponents classes.

1. Create a new View-based Application in Xcode named ATimer.

2. Create a new IBOutlet named timePicker for a UIDateTime and an IBAction called echoTime (Listing 13-4).

Listing 13-4 ATimerViewController.h

#import <UIKit/UIKit.h>
@interface ATimerViewController : UIViewController {
 UIDatePicker * timePicker;
}
@property (nonatomic, retain) IBOutlet UIDatePicker * timePicker;
- (IBAction) echoTime: (id) sender;
- (void) echoIt: (NSTimer *) timer;
 @end

3. Implement the echoTime method so that it implements an NSTimer that fires a timer every second (Listing 13-5).

Listing 13-5 ATimerViewController.m

#import "ATimerViewController.h"
@implementation ATimerViewController
@synthesize timePicker;
NSInteger seconds = 0;
- (IBAction) echoTime: (id) sender {
  NSDate * time = timePicker.date;
  NSDateFormatter * dateFormatter =
     [[[NSDateFormatter alloc] init] autorelease];
  [dateFormatter setDateFormat:@"HH:MM:SS"];
  NSLog(@"date: %@",[dateFormatter stringFromDate:time]);
  NSCalendar *gregorian =
     [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
  NSDateComponents * comps = [gregorian components:(NSHourCalendarUnit |
  NSMinuteCalendarUnit) fromDate:time];
  NSInteger hour = [comps hour];
  NSInteger minute = [comps minute];
  NSLog(@"Hour: %i", hour);
  NSLog(@"minute: %i", minute);
  NSInteger secs = hour * 60 * 60 + minute * 60;
  NSNumber * elapsedSeconds = [[NSNumber alloc] initWithInt:secs];
  NSDictionary * myDict = [NSDictionary dictionaryWithObject:
       elapsedSeconds forKey:@"TotalSeconds"];
  [NSTimer scheduledTimerWithTimeInterval:1 target: self selector: @
  selector(echoIt:) userInfo: myDict repeats: YES];
}
- (void) echoIt: (NSTimer *) timer {
  NSNumber * num =
       (NSNumber *) [[timer userInfo] valueForKey:@"TotalSeconds"];
  seconds++;
  NSInteger secs = [num integerValue] - seconds;
  NSLog(@"elapsed: %i, remaining: %i", seconds, secs);
}
- (void)dealloc {
  [timePicker release];
  [super dealloc];
}
@end

4. Add a method called echoIt for the timer to call when firing.

5. Open ATimerViewController.xib in Interface Builder and place a UIDatePicker on the canvas. Change the picker’s mode to Timer in the Inspector and set the interval to one minute. Also place a button on the canvas (Figure 13-8).

6. Connect the File’s Owner timePicker property to the picker.

Image

Figure 13-8 Adding a button to the canvas

7. Connect the File’s Owner echoTime action to the button’s Touch Up Inside event.

8. Save your changes and click Run (Figure 13-9). The output to the Debugger Console should appear like Listing 13-6.

Listing 13-6 The ATimer application logging to Debugger Console

2010-09-10 11:52:22.658 ATimer[7271:207] date: 01:12:00
2010-09-10 11:52:22.658 ATimer[7271:207] Hour: 1
2010-09-10 11:52:22.659 ATimer[7271:207] minute: 0
2010-09-10 11:52:26.028 ATimer[7271:207] elapsed: 1, remaining: 3599
2010-09-10 11:52:34.723 ATimer[7271:207] elapsed: 2, remaining: 3598
2010-09-10 11:52:43.023 ATimer[7271:207] elapsed: 3, remaining: 3597
2010-09-10 11:52:44.032 ATimer[7271:207] elapsed: 4, remaining: 3596

Image

Figure 13-9 Running the application in iPhone Simulator

This example code involves several concepts not covered elsewhere in this book. Notice the NSDictionary, NSCalendar, and NSDateComponents classes. The UIDatePicker picks the hours and minutes for the timer. When the button is clicked, the echoTime method creates an NSCalendar and obtains date components from the time. After obtaining the total seconds, echoTime adds the seconds to an NSDictionary and creates a timer that fires every second. When firing, the timer calls the echoIt method, passing itself as the parameter. The echoIt method obtains the seconds from the userInfo stored in the timer. The echoIt method then determines the elapsed seconds and logs it to the Debugger Console.

UIPickerView

A UIPickerView allows the selection of a value or values from one or more value sets. A UIPickerView consists of rows and components. Think of the component as the column in a table of values and the row as the row. If you have a three-wheel UIPickerView, the third wheel is the third component. A UIPickerView must have an associated class that adopts the UIPickerViewDelegate and a class that adopts the UIPickerViewDataSource. The same class can adopt both protocols.

UIPickerViewDelegate

The UIPickerViewDelegate protocol dictates how a UIPickerView is to construct itself. This protocol contains five methods a class might implement when adopting this protocol: the picker View:rowHeightForComponent:, pickerView:widthForComponent:, pickerView:titleForRow: forComponent:, pickerView:viewForRow:forComponent:reusingView:, and pickerView:did SelectRow:inComponent: methods.

Width and Height The pickerView:rowHeightForComponent: and pickerView:widthFor Component: methods set a picker’s component dimensions. Remember, a picker’s component can contain rows of strings or view controls, like a UIImageView. These methods accommodate controls by allowing you to set a component’s height and width. Each method’s signature follows.

- (CGFloat)pickerView:(UIPickerView *) pickerView rowHeightForComponent:
(NSInteger) component
- (CGFloat)pickerView:(UIPickerView *) pickerView widthForComponent:
(NSInteger) component

Content The pickerView:titleForRow:forComponent: and pickerView:viewForRow:for Component: methods provide a component’s title or view. The title or the view is what is displayed as the rows in a picker. You must implement one of the two methods. If you need to pick from several strings, implement the pickerView:titleForRow:forComponent: method; if your choices are more complex, use a view for each and implement the pickerView:viewFor Row:forComponent: method. Each method’s signature follows.

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:
(NSInteger) row forComponent: (NSInteger) component
- (UIView *)pickerView:(UIPickerView *)pickerView
viewForRow:(NSInteger) row forComponent:(NSInteger)component
reusingView:(UIView *)view

Selecting The UIPickerView calls the pickerView:didSelectRow:inComponent: method when a user selects a component’s row. It takes the component’s index number and the row’s index number as parameters, so you can determine the component selected and the component’s row. The method’s signature follows.

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row
inComponent:(NSInteger)component
UIPickerViewDatasource

A UIPickerViewDatasource handles a UIPickerView’s data. It contains two methods you should define when adopting this protocol: the numberOfComponentsInPickerView: and pickerView:numberOfRowsInComponent: methods. The numberOfComponentsInPickerView: method returns how many components, or columns, a picker must display.

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent: (NSInteger) component

The pickerView:numberOfRowsInComponent: method returns a component’s row count.

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent: (NSInteger) component

Try This
Using a Picker

1. Create a new View-based Application named APicker.

2. Open APickerViewController.xib in Interface Builder.

3. Drag a picker view from the library to the canvas. Right-click and notice that the control doesn’t have the choices a UIDatePicker has (Figure 13-10).

Image

Figure 13-10 A UIPickerView has fewer outlets and actions than a UIDatePicker.

4. Add a button to the canvas.

5. Save your changes.

6. Create a new NSObject named MyPickerDelegate and change it to adopt the UIPickerViewDelegate and UIPickerViewDataSource protocols. Have MyPickerDelegate implement the needed methods (Listings 13-7 and 13-8).

Listing 13-7 MyPickerDelegate.h

#import <Foundation/Foundation.h>
@interface MyPickerDelegate : NSObject <UIPickerViewDelegate,
UIPickerViewDataSource> {
  NSArray * myData;
}
@property (nonatomic, retain) NSArray * myData;
@end

Listing 13-8 MyPickerDelegate.m

#import "MyPickerDelegate.h"
@implementation MyPickerDelegate
@synthesize myData;
- (id) init {
  if([super init] == nil) return nil;
  myData = [[NSArray alloc] initWithObjects:
      @"Red",@"Yellow",@"Green",@"Blue", @"Purple", @"Orange",
      @"Black", @"Gray", @"Tan", @"Pink", @"Coral", nil];
  return self;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row
              inComponent:(NSInteger)component {
  NSLog(@"picked row: %i, component: %i", row, component);
  NSLog(@"the value: %@", [self.myData objectAtIndex:row]);
}
- (NSInteger) numberOfComponentsInPickerView: (UIPickerView *) pickerView {
  return 1;
}
- (NSInteger) pickerView: (UIPickerView *) pickerView
  numberOfRowsInComponent: (NSInteger) component {
  return [self.myData count];
}

- (NSString *) pickerView: (UIPickerView *) pickerView titleForRow:
          (NSInteger) row forComponent: (NSInteger) component {
  return [self.myData objectAtIndex:row];
}
- (void)dealloc {
  [myData release];
  [super dealloc];
}
@end

7. Open APickerViewController and add IBOutlets for UIPickerView and MyPickerDelegate (Listing 13-9). Add an IBAction called changeColor.

Listing 13-9 APickerViewController.h

#import <UIKit/UIKit.h>
#import "MyPickerDelegate.h"
@class MyPickerDelegate;
@interface APickerViewController : UIViewController {
 UIPickerView * myPicker;
 MyPickerDelegate * myPickerDelegate;
}
@property (nonatomic, retain) IBOutlet UIPickerView * myPicker;
@property (nonatomic, retain) IBOutlet MyPickerDelegate *
myPickerDelegate;
- (IBAction) changeColor: (id) sender;
@end

8. Implement the changeColor action (Listing 13-10).

Listing 13-10 APickerViewController.m

#import "APickerViewController.h"
#import "MyPickerDelegate.h"
@implementation APickerViewController
@synthesize myPicker;
@synthesize myPickerDelegate;
- (IBAction) changeColor: (id) sender {
  NSLog(@"the color is: %@", (NSString *)[myPickerDelegate.myData
objectAtIndex: [myPicker selectedRowInComponent:0]]);
}
- (void)dealloc {
  [myPickerDelegate release];
  [myPicker release];
  [super dealloc];
}
@end

9. Save and open APickerViewController.xib in Interface Builder.

10. Connect the File’s Owner changeColor action to the button’s Touch Up Inside event.

11. Connect the File’s Owner myPicker outlet to the UIPickerView.

12. Drag an object from the library to the editing pane. Change the object’s type to MyPickerDelegate.

13. Connect the File’s Owner myPickerDelegate outlet to the object just added.

14. Connect the UIPickerView’s dataSource and delegate outlets to the newly added MyPickerDelegate object.

15. Save your changes and click Run to run the application in iPhone Simulator. When the button is pushed, the Debugger Console logs the picker’s chosen color (Figure 13-11 and Listing 13-11).

Image

Figure 13-11 Running the application in iPhone Simulator

Listing 13-11 Debug Console output from running APicker application

2010-09-10 12:13:43.184 APicker[7497:207] picked row: 1, component: 0
2010-09-10 12:13:43.188 APicker[7497:207] the value: Yellow
2010-09-10 12:13:45.123 APicker[7497:207] the color is: Yellow
2010-09-10 12:13:54.053 APicker[7497:207] picked row: 2, component: 0
2010-09-10 12:13:54.054 APicker[7497:207] the value: Green
2010-09-10 12:13:55.037 APicker[7497:207] the color is: Green

A UIPickerView must have helper classes adopting the UIPickerViewDelegate and UIPickerViewDataSource protocols. In this example you had one class, MyPickerDelegate, adopt both protocols. The delegate uses a simple NSArray to hold NSString objects. Because the data is simple strings, the delegate implements the titleForRow method. When a user selects a row, the didSelectRow method logs the row, component, and value to the Debugger Console.

Try This
Using a UIPickerView with Two Components

1. Make a copy of the APicker project you just finished and then open it in Xcode.

2. Modify MyPickerDelegate’s numberOfComponentsInPickerView to return the number 2 (Listing 13-13).

3. Click Build And Go. Notice the picker now shows two independent spinning wheels (Figure 13-12).

4. Add a second value array. Call the array myData2 and initialize it in the init method, as you did before with myData (Listings 13-12 and 13-13).

Listing 13-12 MyPickerDelegate.h modified to reflect two wheels

#import <Foundation/Foundation.h>
#define COLOR_WHEEL 0
#define SHADE_WHEEL 1
@interface MyPickerDelegate : NSObject <UIPickerViewDelegate,
UIPickerViewDataSource> {
  NSArray * myData;
  NSArray * myData2;
}
@property (nonatomic, retain) NSArray * myData;
@property (nonatomic, retain) NSArray * myData2;
@end

Image

Figure 13-12 A UIPickerView with two components

Listing 13-13 MyPickerDelegate.m modified to reflect two wheels

#import "MyPickerDelegate.h"
@implementation MyPickerDelegate
@synthesize myData;
@synthesize myData2;
- (id) init {
  if([super init] == nil)
   return nil;
  myData = [[NSArray alloc]initWithObjects:
     @"Red", @"Yellow", @"Green", @"Blue",
     @"Purple", @"Orange", @"Black", @"Gray", @"Tan", @"Pink", @"Coral", nil];
  myData2 = [[NSArray alloc] initWithObjects:
     @"Very Dark", @"Dark", @"Normal", @"Light", @"Very Light", nil];
  return self;
}
- (NSInteger) numberOfComponentsInPickerView: (UIPickerView *) pickerView {
  return 2;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:
       (NSInteger)row inComponent:(NSInteger)component {
  NSLog(@"picked row: %i, component: %i", row, component);
  if(component == COLOR_WHEEL)
    NSLog(@"the value: %@", [self.myData objectAtIndex:row]);
  else
    NSLog(@"the value: %@", [self.myData2 objectAtIndex:row]);
  }
- (NSInteger) pickerView: (UIPickerView *)pickerView
       numberOfRowsInComponent (NSInteger) component {
  if(component == COLOR_WHEEL)
    return [self.myData count];
  else
    return [self.myData2 count];
}
- (NSString *) pickerView: (UIPickerView *) pickerView
       titleForRow: (NSInteger) row forComponent: (NSInteger) component {
  if(component == COLOR_WHEEL)
     return [self.myData objectAtIndex:row];
  else
     return [self.myData2 objectAtIndex:row];
}
- (void)dealloc {
  [myData release];
  [myData2 release];
  [super dealloc];
}
@end

5. Create two constants representing the different wheels: COLOR_WHEEL for the wheel containing the myData values and SHADE_WHEEL for the wheel containing the myData2 values (Listing 13-12).

6. Modify the numberOfRowsInComponent method and titleForRow method to reflect the newly added wheel.

7. Open APickerViewController.m and modify the changeColor method to reflect the second wheel (Listing 13-14).

Listing 13-14 The changeColor method modified to reflect two wheels

- (IBAction) changeColor: (id) sender {
  NSLog(@"the color is: %@ and the shade is: %@",
    (NSString *)[myPickerDelegate.myData objectAtIndex:
             [myPicker selectedRowInComponent: COLOR_WHEEL]],
    (NSString *)[myPickerDelegate.myData2 objectAtIndex:
             [myPicker selectedRowInComponent:SHADE_WHEEL]]); }

8. Save your changes and click Run. The application shows two wheels. Upon clicking the button, the Debugger Console logs the first wheel’s color and the second wheel’s shade (Figure 13-13 and Listing 13-15).

Image

Figure 13-13 Running the application in iPhone Simulator

Listing 13-15 Debugger Console logging from running APicker application

2010-09-10 12:26:48.705 APicker[7655:207] picked row: 1, component: 0
2010-09-10 12:26:48.715 APicker[7655:207] the value: Yellow
2010-09-10 12:26:49.658 APicker[7655:207] picked row: 2, component: 1
2010-09-10 12:26:49.660 APicker[7655:207] the value: Normal
2010-09-10 12:26:50.964 APicker[7655:207] the color is: Yellow and the
shade is: Normal

Using more components involves adding code to check which component was selected. Note that rather than use the raw integers, you created constants for both components, producing more readable code. Each delegate’s method then checks which component the user selected.

if(component == COLOR_WHEEL)
  return [self.myData objectAtIndex:row];
else
  return [self.myData2 objectAtIndex:row];

Try This
Loading UIImageViews into a UIPickerView

1. Make a copy of the original APicker project created earlier—not the project with two components, but the earlier project with only one component. Open it in Xcode.

2. Replace the pickerView:titleForRow:forComponent: method in MyPickerDelegate with pickerView:viewForRow:forComponent: (Listing 13-16).

Listing 13-16 MyPickerDelegate.m modified to load images into the UIPickerView

#import "MyPickerDelegate.h"
@implementation MyPickerDelegate
@synthesize myData;
- (id) init {
  if([super init] == nil)
     return nil;
  UIImageView * one = [[UIImageView alloc] initWithImage:
         [[UIImage alloc] initWithContentsOfFile:
              [[[NSBundle mainBundle] resourcePath]
                   stringByAppendingPathComponent:@"wizard.png"]]];
  UIImageView * two =[[UIImageView alloc] initWithImage:
          [[UIImage alloc] initWithContentsOfFile:
              [[[NSBundle mainBundle] resourcePath]
                   stringByAppendingPathComponent: @"tux.png"]]];
  UIImageView * three =[[UIImageView alloc] initWithImage:
          [[UIImage alloc] initWithContentsOfFile:
              [[[NSBundle mainBundle] resourcePath]
                   stringByAppendingPathComponent:@"money.png"]]];
  myData = [[NSArray alloc] initWithObjects:one,two,three,nil];
  return self;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:
            (NSInteger) row inComponent: (NSInteger)component {
  NSLog(@"picked row: %i, component: %i", row, component);
}
- (NSInteger) numberOfComponentsInPickerView:
               (UIPickerView *)pickerView {
  return 1;
}
- (NSInteger) pickerView:
        (UIPickerView *) pickerView
         numberOfRowsInComponent: (NSInteger) component {
  return [self.myData count];
}
- (UIView *)pickerView:(UIPickerView *)pickerView
         viewForRow:(NSInteger) row
         forComponent:(NSInteger)component
         reusingView:(UIView *)view {
  return [self.myData objectAtIndex:row];
}
- (void)dealloc {
  [myData release];
  [super dealloc];
}
@end

3. Add the images money.png, wizard.png, and tux.png to the project. You can find these images in the book’s resources folder.

4. Modify the MyPickerDelegate’s init: method to load UIImageViews rather than strings into myData (Listing 13-16).

5. Modify the pickerView:didSelectRow:inComponent: to only log the row and component to the Debugger Console (Listing 13-16).

6. Save your changes and open APickerViewController.xib and remove the button.

7. Save your changes, and click Run. The application loads the images into the UIPickerView (Figure 13-14).

Image

Figure 13-14 A UIPickerView that uses UIImageView objects as its components

NOTE
Notice that the pickerView:viewForRow:forComponent method takes a UIView. So, much as when working with custom table cells, you can also create custom picker rows using a UIView.

Using the Camera: UIImagePickerController

Rather than working directly with an iPhone’s camera, you use the UIImagePickerController to manipulate an iPhone’s camera and photo library. Using the UIImagePickerController, you take, or select, a photo, optionally edit the photo, and then dismiss the UIImagePickerController, returning control back to your application.

UIImagePickerController

The UIImagePickerController is different from other view controllers. Rather than developers creating the controller’s view and adding components to the view’s canvas, the UIImage PickerController’s views are already created and are part of the UIKit library. Developers simply determine the controller’s source type and implement a delegate for the controller. The controller creates and manages the views, while the delegate responds to the view being dismissed by a user.

Source

Earlier models of the iPod touch, the original iPad, and the iPhone Simulator all lack a camera. If you attempt to use a device’s nonexistent camera, you will get an exception, and if that exception isn’t caught, the application will terminate unexpectedly. If you’re attempting to use the camera on an iPhone Simulator, it will also log a message to the Debugger Console.

2010-09-10 13:53:16.010 CameraProject[8418:207] *** Terminating app
due to uncaught exception 'NSInvalidArgumentException', reason:
'Source type 1 not available'

To avoid an exception or an application that unexpectedly quits, the UIImagePickerController provides the isSourceTypeAvailable: method.

+ (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)
sourceType

This method returns YES if a source type is available and NO if unavailable. Valid source types are UIImagePickerControllerSourceTypePhotoLibrary, for selecting images from the photo library; UIImagePickerControllerSourceTypeCamera, for selecting images from the camera; and UIImagePickerControllerSourceTypeSavedPhotosAlbum, for selecting images from a camera roll, or from the photo library if the device doesn’t have a camera.

After ensuring a device has a source type, you set the UIImagePickerController’s sourceType property. This property determines what controls the UIImagePickerController displays. Allowable source types are the same as with the isSourceTypeAvailable: method.

Editing and Delegating

The controller also has an allowsEditing property and delegate property (note that prior to iOS 3.1 this property was called allowsImageEditing). The allowsEditing property determines if a user should be allowed to edit an image after taking or selecting the image. The delegate property specifies the class’s UIImagePickerControllerDelegate.

UIImagePickerControllerDelegate

The UIImagePickerControllerDelegate’s protocol has two methods your delegate should implement for the image picker. The imagePickerController:didFinishPickingMediaWithInfo :info: method is called after a user selects an image. This could be selecting an image from a camera roll or photo library, or after taking a photo using the camera. The method’s signature follows.

- (void)imagePickerController:(UIImagePickerController *) picker
didFinishPickingMediaWithInfo: (NSDictionary *) info;

If you cancel a photo selected, the imagePickerControllerDidCancel: method is called. The method’s signature follows:

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker

The imagePickerController:didFinishPickingMediaWithInfo:info: has three parameters. The first parameter holds a reference to the image picker, the second to the image picked, and the third to an NSDictionary containing editing information. If editing is disabled, the third parameter holds nil. If editing is enabled, the parameter holds the unedited image and the cropped rectangle. For more information on the imagePickerController:didFinish PickingMediaWithInfo:info method, refer to Apple’s online “UIImagePickerControllerDelegate Reference.”

Try This
Using the UIImagePickerController

NOTE
Using the camera or camera roll requires having an iPhone or iPod with a camera and running the application on the device, which will require a paid developer’s membership, and a provision profile for this example application. Even if you can’t try the camera part of this example, you can still select a photo from a photo album.

1. Create a new View-based Application. Name the project CameraProject.

2. Open CameraProjectViewController.xib in Interface Builder.

3. Drag a toolbar from the library onto the view’s canvas.

4. Rename the first button Take Photo. Add another button to the toolbar and call it Select Photo. Add a UIImageView to the canvas (Figure 13-15).

5. Save your changes.

6. Create a new class called MyImagePickerDelegate derived from NSObject. Modify the class so that it adopts the UINavigationControllerDelegate and UIImagePickerControllerDelegate protocols (Listing 13-17). Add a property that contains the UIImage that the image picker will select.

Image

Figure 13-15 The application’s canvas

Listing 13-17 MyImagePickerDelegate.h

#import <Foundation/Foundation.h>
@interface MyImagePickerDelegate : NSObject
<UINavigationControllerDelegate, UIImagePickerControllerDelegate> {
  UIImage * selectedImage;
}
@property (nonatomic, retain) UIImage * selectedImage;
@end

7. Implement the imagePickerController methods (Listing 13-18).

Listing 13-18 MyImagePickerDelegate.m

#import "MyImagePickerDelegate.h"
@implementation MyImagePickerDelegate
@synthesize selectedImage;
- (void) imagePickerControllerDidCancel:
          (UIImagePickerController *) picker {
  [picker.parentViewController dismissModalViewControllerAnimated:YES];
  [picker release];
}
- (void)imagePickerController:(UIImagePickerController *) picker
             didFinishPickingMediaWithInfo:(NSDictionary *) info {
  self.selectedImage = (UIImage*)[info objectForKey:
          UIImagePickerControllerOriginalImage];
  [picker.parentViewController
             dismissModalViewControllerAnimated: YES];
  [picker release];
}
- (void) dealloc {
  [selectedImage release];
  [super dealloc];
}
@end

8. Open CameraProjectViewController.h and add a forward reference to MyImagePickerDelegate. Add an IBOutlet for MyImagePickerDelegate, the UIImageView and the takePhoto UIBarButtonItem (Listing 13-19).

Listing 13-19 CameraProjectViewController.h

#import <UIKit/UIKit.h>
@class MyImagePickerDelegate;
@interface CameraProjectViewController : UIViewController {
  MyImagePickerDelegate * imgPickerDelegate;
  UIImageView * theImageView;
  UIBarButtonItem * theTakePhotoButton;
}
@property (nonatomic, retain) IBOutlet
     MyImagePickerDelegate *imgPickerDelegate;
@property (nonatomic, retain) IBOutlet UIImageView * theImageView;
@property (nonatomic, retain) IBOutlet
     UIBarButtonItem *theTakePhotoButton;
- (IBAction) takePicture: (id) sender;
- (IBAction) selectPicture: (id) sender;
@end

9. Add two IBActions to CameraProjectViewController. Name one action takePicture and the other selectPicture (Listing 13-19). Implement both methods as shown in Listing 13-20.

Listing 13-20 CameraProjectViewController.m

#import "CameraProjectViewController.h"
#import "MyImagePickerDelegate.h"
@implementation CameraProjectViewController
@synthesize imgPickerDelegate;
@synthesize theImageView;
@synthesize theTakePhotoButton;
- (void) viewDidLoad {
  [super viewDidLoad];
  // Only enable the button if a camera is actually available
  theTakePhotoButton.enabled = ([UIImagePickerController
          isSourceTypeAvailable:
          UIImagePickerControllerSourceTypeCamera]);
}
- (IBAction) takePicture: (id) sender {
  UIImagePickerController * pickCont =
      [[UIImagePickerController alloc] init];
  pickCont.delegate = imgPickerDelegate;
  pickCont.allowsEditing = YES;
  pickCont.sourceType = UIImagePickerControllerSourceTypeCamera;
  [self presentModalViewController:pickCont animated:YES];
  NSLog(@"heynow");
  if(self.imgPickerDelegate.selectedImage != nil)
     self.theImageView.image = self.imgPickerDelegate.selectedImage;
}

- (IBAction) selectPicture: (id) sender {
  UIImagePickerController * pickCont =
      [[UIImagePickerController alloc] init];
  pickCont.delegate = imgPickerDelegate;
  pickCont.allowsEditing = YES;
  pickCont.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  [self presentModalViewController:pickCont animated:YES];
  NSLog(@"heynow");
  if(self.imgPickerDelegate.selectedImage != nil)
     self.theImageView.image = self.imgPickerDelegate.selectedImage;
  }

- (void)dealloc {
  [theImageView release];
  [imgPickerDelegate release];
  [super dealloc];
}
@end

10. Implement the viewDidLoad method in CameraProjectViewController so that we enable the Take Photo button only when the camera is available (Listing 13-20).

11. Save your changes.

12. Open CameraProjectViewController.xib in Interface Builder.

13. Connect the File’s Owner theImageView outlet to the UIImageView on the canvas. Connect the File’s Owner theTakePhotoButton IBOutlet to the Take Photo bar item.

14. Connect the selectPicture action to the Select Photo button and the takePicture action to the Take Photo button.

15. Drag an object from the library to the Document window. Change its type to MyImagePickerDelegate.

16. Connect the File’s Owner imgPickerDelegate outlet to the newly created object.

17. Save your changes.

18. If you want to use the camera, follow the necessary steps to register and provision the application so that you can install it on your iPhone. Otherwise, you can use the Select Photo button and select a photo from the simulator’s photo albums.

19. Run the application. Everything works as expected, except notice that the view’s image is not set to the image selected by the image picker.

Nothing happens after selecting or taking a photo, unless you click one of the application’s buttons a second time. The photo previously selected is then displayed (Figure 13-16). What is supposed to happen is that the theImageView in CameraProjectViewController should display the newly taken photo. You might have thought that placing the following lines in the selectPicture method would have worked, but they didn’t.

if(self.imgPickerDelegate.selectedImage != nil)
  self.theImageView.image = self.imgPickerDelegate.selectedImage;

Notice the “heynow” logging added to the delegate’s methods. Immediately after displaying the image picker, the Debugger Console logs “heynow.” If the logging does not wait for the image picker to finish, the two lines setting the image don’t wait either, and so the image isn’t set correctly until the next time you push one of the application’s two buttons.

CameraProjectController must be changed to be notified when a picture is taken so that it can update its view. There are two ways you might fix the image not displaying. First, you could add a reference to the CameraProjectViewController in MyImagePickerDelegate, but that isn’t an ideal solution. Why? Adding a CameraProjectViewController to MyImagePickerDelegate as a property introduces close coupling between the two classes. You can never again reuse MyImagePickerDelegate, unless you reuse CameraProjectViewController. Using a notification would be a better solution.

Image

Image

Figure 13-16 Running the application in iPhone Simulator and using the provided photo library

Notifications

If you have ever used Java listeners, you already know the general idea behind notifications. Cocoa’s notifications are similar to Java’s listeners, only easier and more flexible. Every Cocoa application has a notification center, NSNotificationCenter. Classes in your application can post notifications, NSNotification, to the notification center. Other classes can register with the notification center to listen for notifications. Through notifications, classes can communicate with each other without even knowing about one another’s existence. Although this book doesn’t present notifications in any detail, in the next few steps, you add a notification to the CameraProjectViewController.

NOTE
Refer to Apple’s Introduction to Notification Programming Topics for more information on using notifications.

Try This
Using Notifications

1. Reopen CameraProject in Xcode.

2. Modify the imagePickerController:didFinishPickingMediaWithInfo:info: method to post a notification (Listing 13-21).

Listing 13-21 The didFinishPickingMediaWithInfo modified to post a notification

- (void)imagePickerController:(UIImagePickerController *) picker
         didFinishPickingMediaWithInfo:(NSDictionary *) info {
  self.selectedImage = (UIImage*)[info
  objectForKey:UIImagePickerControllerOriginalImage];
  [[NSNotificationCenter defaultCenter] postNotificationName:
        @"Image Picked" object:nil];
  [picker.parentViewController dismissModalViewControllerAnimated:YES];
  [picker release];
}

3. Add a method called changeImage to CameraProjectViewController (Listing 13-22).

Listing 13-22 The changeImage method

- (void) changeImage {
  NSLog(@"IMAGE CHANGED");
  self.theImageView.image = self.imgPickerDelegate.selectedImage;
}

4. Modify viewDidLoad to observe the notification (Listing 13-23). Also, modify dealloc to unregister the controller as a notification listener.

Listing 13-23 The viewDidLoad method modified to have CameraViewController observe the notification

-(void) viewDidLoad {
 [super viewDidLoad];
 theTakePhotoButton.enabled =([UIImagePickerController
        isSourceTypeAvailable:
UIImagePickerControllerSourceTypeCamera]);
 [[NSNotificationCenter defaultCenter] addObserver:self selector:
       @selector(changeImage) name:@"ImagePicked" object:nil];
}
-(void) dealloc {
 [super dealloc];
 [theImageView release];
  [imgPickerDelegate release];
 [[NSNotificationCenter defaultCenter] removeObserver:self];
}

5. Save your changes and click Run. Now the application sets the UIImageView’s image as expected.

NOTE
Notifications worked well in the preceding Try This example, but another way of achieving the same result would be to use an observer. Refer to Apple’s Introduction to Notification Programming Topics for more information on using observers.

In the last two tasks, you created an application that uses the UIImagePickerController to control an iPhone’s camera. You created two buttons, one for taking a photo using the camera and one for selecting an image from the photo library. If you obtained provisioning for the application and installed the provisioning on your iPhone, you could install the application on your iPhone and use the camera (Figure 13-17). Alternatively, you selected photos from the photo library while running the application on the iPhone Simulator. You used isSourceTypeAvailable to only enable the Take Photo button when a camera is available. Depending on the design of your application, rather than disabling a button, it could make more sense to only display a button for taking a picture if the camera is present.

Image

Figure 13-17 Using the camera on an iPhone

Summary

In this chapter, you used a UIDatePicker, a UIPickerView, and a UIImagePickerController. A UIDatePicker is for selecting a date and time. You used a UIDatePicker to select a date and to select a time interval. After the UIDatePicker, you learned about UIPickerViews. A UIPickerView is for selecting a string value or an object descending from UIView. You used a UIPickerView with one component and then used one with two components. You then modified the one-component UIPickerView to display images rather than strings. After examining the UIDatePicker and UIPickerView controls, you learned about the UIImagePickerController. This class allows you to select images from an iPhone’s camera, camera roll, or photo album. Only the third option, selecting from a photo album, works on the iPhone Simulator or earlier iPod Touch models, as neither has a camera. In this chapter’s final task, you used a UIImagePickerController to select a photo from the iPhone Simulator’s photo album.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset