Bookmark and Share

iPhone SDK Tutorial
Chapter 3. Image View, Text Field, Slider, Segment Control, Switch, and Action Sheet




3.1 Preview

In this tutorial, we will add more items to our application such as text field, slider, and switch.

This chapter is largely based on Beginning iPhone 3 Development

The pictures below show two modes of display which are the two main displays of this tutorial. Picture in the left is displaying two switches when the switch segment control is clicked while the right side picture triggered by the Button segment control is displaying a button supposed to do something in the future.


Ch3 Final Picicture A Ch3 Final Picicture B


Following two pictures are "action sheet" and "alert". When the "Do Risky Thing" button pressed, the "action sheet" shows up and at the "Yes" button, the user gets the "alert".

Ch3 Final Picicture C Ch3 Final Picicture D

3.2 Image View

Add your image to the Resources folder by dragging the image or by selecting "Add to Project..." under Project top menu.



Open up Interface Builder by double clicking "MoreUIViewController.xib" under Class of Groups & Files.

Drag the Image View from the Library window onto the window called View.


UI Image View With Nothing

Hierarchial View Subview

While the image view selected as in the picture, open Inspector Window under Tools of Interface Builder.


Inspector Image View

But the picture is too big. So, we need to resize it. Select "Size to Fit" under Layout menu.
The pictures below shows before and after resizing.
The picture in the right side has been repositioned.
(Note) Selecting the image can be done by double-clicking the "Image View" which is in ViewController.xib window.



Too Big Tiger Right Size Tiger


3.3 Text Fields and Labels

From the Library window, drag two Text Fields and two Labels into the View window.
After making changes to the background color of the tiger and View, the result should look like the picture below.

Drag Two Text Fields And Labels

Text fields are one of the most complex controls on the iPhone as well as being one of the most used.

Let's look at the attributes of the text field. Select the Name field and choose Inspector under Tools menu of Interface Builder. Here, I divided the Inspector by two parts, top and bottom sections.

Text Field Inspector A Text Field Inspector B

Name field

  • Text Field Section
    • The Text field allows you to set a default value.
    • The Placeholder field allows you to set a text that will be displayed when the field has no value.
  • Text Input Traits Section
    This section defines how the keyboard will look and behave.
    • Capitalize drop down to Words.
      This will cause every word to be automatically capitalized.
    • Return Key set to Done

Number field

  • In the Placeholder field, type "Type in a number"
  • Uncheck "Clear When Editing Begins"
  • Click the "Keyboard Type", select "Number Pad"

To set outlets, let's modify our code a little bit.

File: "MoreUIViewController.h"

#import <UIKit/UIKit.h>

@interface MoreUIViewController : UIViewController {
    UITextField	*nameField;
    UITextField *numberField;
}
@property (nonatomic, retain) IBOutlet UITextField *nameField;
@property (nonatomic, retain) IBOutlet UITextField *numberField;
@end

File: "MoreUIViewController.m"

#import "MoreUIViewController.h"

@implementation MoreUIViewController
@synthesize nameField;
@synthesize numberField;

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
}

- (void)dealloc {
	[nameField release];
	[numberField release];    
    [super dealloc];
}
@end

Now, let's hook up our outlets. Control-drag "File's Owner" to each of the text fields. Then, connect them to their corresponding outlets. Save the nib file. Build and Run.



3.4 Keyboard

How do get the keyboard away when "Done" is tapped?

When the user taps the "Done", a "Did End On Exit" event will be generated. So, we need to tell the text field to give up control to make the keyboard go away. We need to add an action method for this.

File: "MoreUIViewController.h"

#import <UIKit/UIKit.h>

@interface MoreUIViewController : UIViewController {
    UITextField	*nameField;
    UITextField *numberField;
}
@property (nonatomic, retain) IBOutlet UITextField *nameField;
@property (nonatomic, retain) IBOutlet UITextField *numberField;
- (IBAction)textFieldDoneEditing:(id)sender;
@end

After we add folowing method to .m file:

- (IBAction)textFieldDoneEditing:(id)sender {
	[sender resignFirstResponder];
}

File: "MoreUIViewController.m"

#import "MoreUIViewController.h"

@implementation MoreUIViewController
@synthesize nameField;
@synthesize numberField;

- (IBAction)textFieldDoneEditing:(id)sender {
	[sender resignFirstResponder];
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)dealloc {
	[nameField release];
	[numberField release];    
    [super dealloc];
}
@end

Build and Run, still Keyboard is not going away at "Done" tap.

This is related to the concept of "first responder". It's the control that the user is currently interacting with. When a text field yields its first responder status, the keyboard, which is associated with text field goes away.
Go back to Interface Builder. Single-click the Name text field and open up the connections inspector under Tools menu.
What's the event that will fire when the user taps the "Done" button?
It's "Did End On Exit".
Drag from the circle next to "Did End On Exit" to the File's Owner icon, and connect it to the "textFieldDoneEditing" action. Repeat with the other text field.
The picture below shows the connections inspector window after it's done.

Did End On Exit Con Inspector

Build and Run. This time, keyboard goes away when we taps "Done" button.
But not for the number pad. There's no "Done" button on it.
As a solution to this problem, we can make the number pad go away by tapping other places of the view. Actually, the view controller has a property called view which it inherited from UIViewController.
This view property corresponds to the "view icon" in the nib file. This property points to an instance of UIView in the nib which acts as a container for all the items in our UI. It has no appearance in the user interface. But it covers the entire iPhone window, sits "below" all because its main purpose is to simply hold other views and controls.
For all intents and purposes, the container view is the background of our user interface.

In the Interface Builder, we can change the class of the object that view points to so that its underlying class becomes UIControl not UIView. Because UIControl is a subclass of UIView, it is appropriate for us to connect our view property to an instance of UIControl.

Before we go to Interface Builder, we should make an action method for tapping background.

3.5 Handling Background Touch

Let's add some line of code to "MoreUIViewController.h"

- (IBAction)backgroundTap:(id)sender;

and to "MoreUIViewController.m" these lines:

- (IBAction)backgroundTap:(id)sender {
	[nameField resignFirstResponder];
	[numberField resignFirstResponder];	
}

The code we added tells both text fields to yield first responder status if they have it when the user taps background.

Now, go back to Interface Builder.
We need to change the underlying class of nib's view. As we see the main window, the 3rd icon, called view, is our nib's main view that holds all the other controls and views as subviews. It's nib's container view.

nib's Main Window

Select the View and open up identity inspector from Tools menu. From the picture below, change the field labeled Class to UIControl from UIView.

Identity Inspector Of View

All controls which are subclasses of UIControl can trigger action methods. So, by changing the underlying class, we have just given this view the ability to trigger action methods. Then, you will see that our background have awarded new events as shown in the picture for Control Connection below.

UIControlB

Compare that with this picture which is before the class change.

UIControlA

And also look at the MoreUIViewControl.xib. The name has been changed to UIControl from UIView as we expected.

Changed From UIView To UIControl

Yes, we have controls now.

We have one last thing to do related to the background tapping. Among the new events we got, drag the Touch Down event to the File's Owner icon, and choose the backgroundTap: action so that from now on, touching anywhere without an active control will trigger our new method, backgroundTab:. And that will cause the keyboard and number pad to retreat.

Save the nib, go to XCode and Build and Run.



3.6 Slider

We are going to add a slider and label to display the value of the slider. The label will need to be changed as the slider moves. That means we need an outlet for the label. When the slider moves, we need to get some information from the slider via action method. The method triggered by the slider will receive a pointer to the slider in the sender argument. So, we will be able to retrieve the slider's value from sender.

We need to add following lines of code to "MoreUIViewController.h"

    
UILabel     *sliderLabel;
@property (nonatomic, retain) IBOutlet UILabel *sliderLabel;
- (IBAction)sliderChanged:(id)sender;

and a method

- (IBAction)sliderChanged:

to the implementation file "MoreUIViewController.m".

So, following two files are the result.

File "MoreUIViewController.h"

#import <UIKit/UIKit.h>

@interface MoreUIViewController : UIViewController {
	UITextField	*nameField;
    UITextField *numberField;
	UILabel     *sliderLabel;
    }
@property (nonatomic, retain) IBOutlet UITextField *nameField;
@property (nonatomic, retain) IBOutlet UITextField *numberField;
@property (nonatomic, retain) IBOutlet UILabel *sliderLabel;
- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)backgroundTap:(id)sender;
- (IBAction)sliderChanged:(id)sender;
@end

File "MoreUIViewController.m"

#import "MoreUIViewController.h"

@implementation MoreUIViewController
@synthesize nameField;
@synthesize numberField;
@synthesize sliderLabel;

- (IBAction)sliderChanged:(id)sender {
    UISlider *slider = (UISlider *)sender;
    int progressAsInt = (int)(slider.value + 0.5f);
    NSString *newText = [[NSString alloc] initWithFormat:@"%d", 
						 progressAsInt];
    sliderLabel.text = newText;
    [newText release];
}

- (IBAction)textFieldDoneEditing:(id)sender {
	[sender resignFirstResponder];
}

- (IBAction)backgroundTap:(id)sender {
	[nameField resignFirstResponder];
	[numberField resignFirstResponder];	
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)dealloc {
	[nameField release];
	[numberField release];	
	[sliderLabel release];
	[super dealloc];
}

@end

Coding is done. Let's move on to Interface Builder.

Bring over a slider and label from the library. Set the max., min, and initial value in the attribute inspector window for the slider as in the picture below.

Slider Attribute Inspector

For the label, set it to 50.

Now, it's time to connect the action method to the outlets. Control-drag from the File's Owner icon to the label. Select sliderLabel. How about the slider itself. What event we should use?

Open up connection inspector under Tools menu after selecting the slider. The event we need is Value Changed. Drag its circle to File's Owner icon, and select sliderChanged.

OK. Save the nib, and Build and Run. Slider is done now.


3.7 Switches and Segment Control

We need to create the outlets for the parent view and outlet for the two switches. The segment control should trigger an action method that will hide and show the view containing the switches and labels. We also need an action that trigger when the switches tapped.

Modified codes are as below.

File "MoreUIViewController.h"

#import <UIKit/UIKit.h>
#define kSwitchesSegmentIndex    0
@interface MoreUIViewController : UIViewController {
    UITextField	*nameField;
    UITextField *numberField;
    UILabel     *sliderLabel;
    UISwitch	*leftSwitch;
    UISwitch	*rightSwitch;
    UIButton	*doRiskyThingButton;
}
@property (nonatomic, retain) IBOutlet UITextField *nameField;
@property (nonatomic, retain) IBOutlet UITextField *numberField;
@property (nonatomic, retain) IBOutlet UILabel *sliderLabel;
@property (nonatomic, retain) IBOutlet UISwitch *leftSwitch;
@property (nonatomic, retain) IBOutlet UISwitch *rightSwitch;
@property (nonatomic, retain) IBOutlet UIButton	*doRiskyThingButton;
- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)backgroundTap:(id)sender;
- (IBAction)sliderChanged:(id)sender;
- (IBAction)toggleControls:(id)sender;
- (IBAction)switchChanged:(id)sender;
- (IBAction)buttonPressed;
@end

File "MoreUIViewController.m"

#import "MoreUIViewController.h"

@implementation MoreUIViewController
@synthesize nameField;
@synthesize numberField;
@synthesize sliderLabel;
@synthesize leftSwitch;
@synthesize rightSwitch;
@synthesize doRiskyThingButton;

- (IBAction)toggleControls:(id)sender {    
    if ([sender selectedSegmentIndex] == kSwitchesSegmentIndex)
    {
        leftSwitch.hidden = NO;
        rightSwitch.hidden = NO;
        doRiskyThingButton.hidden = YES;
    }
    else
    {
        leftSwitch.hidden = YES;
        rightSwitch.hidden = YES;
        doRiskyThingButton.hidden = NO;
    }
}

- (IBAction)switchChanged:(id)sender {
    UISwitch *whichSwitch = (UISwitch *)sender;
    BOOL setting = whichSwitch.isOn;
    [leftSwitch setOn:setting animated:YES];
    [rightSwitch setOn:setting animated:YES];
}

- (IBAction)buttonPressed {
}

- (IBAction)sliderChanged:(id)sender {
    UISlider *slider = (UISlider *)sender;
    int progressAsInt = (int)(slider.value + 0.5f);
    NSString *newText = [[NSString alloc] initWithFormat:@"%d", 
						 progressAsInt];
    sliderLabel.text = newText;
    [newText release];
}

- (IBAction)textFieldDoneEditing:(id)sender {
    [sender resignFirstResponder];
}

- (IBAction)backgroundTap:(id)sender {
    [nameField resignFirstResponder];
    [numberField resignFirstResponder];	
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
    // Release any cached data, images, etc that aren't in use.
}

- (void)dealloc {
    [nameField release];
    [numberField release];	
    [sliderLabel release];
    [leftSwitch release];
    [rightSwitch release];
    [doRiskyThingButton release];
    [super dealloc];
}

@end

The first method, toggleConrols: is called when the segment control is tapped. The second method, switchChanged: is called when one of the two switches is tapped. Also in the code do not forget to release the outlets.

Now, it's time to add the switches, buttons, and segment control to our view.

Drag a Segment Control and two Switches to our control window. Changes the labels of the Segment Control to Switches and Button from First and Second as shown in the picture below.

Segment Control Switches

Let's connect the two switches to File's Owner's icon and select appropriate outlets. Next thing to do is to set right method for switch outlet. Open up connections inspector. Drag from the Value Changed event to the File's Owner icon, and select the switch Changed: action as in the picture.

Value Changed Switch Changed

Do this again for the right side switches. Select segment control, and fire connections inspector, and the circle next to Value Changed event to the File's Owner icon. Then select the toggleControls: action method.

Let's put a button at the same location of the two switches. Label it "Do Risky Thing" and make it hidden by checking Hidden checkbox of the attribute inspector.

Add Button And Make It Hidden

Again, time to make connection to outlets.
Control-drag from File's Owner to the new button, and select the doRiskyThingButton outlet. Then, go to the connections inspector and drag from the circle of Touch Up Inside event to File's Owner. Select the buttonPressed action.

Save nib. Build and Run.

Let's check if we did it right. When we tap the Switches segment, the pair of switches should appear. Tapping one of the switches, both switches should toggle. At the tap of the Button segment. the switches should be hidden, replaced by the Do Risky Thing button. Tapping the button does not do anything yet because we haven't implemented method for tapping button.


3.8 Action Sheet and Alert

Action sheets and alerts both use delegates so that they know what object to notify when they are done. In our case, we are going to need to get notified when the action sheet is dismissed. In order for our controller class to act as the delegate for an action sheet, it needs to conform to a protocol called UIActionSheetDelegate. We do that by adding to the interface file.

#import <UIKit/UIKit.h>
#define kSwitchesSegmentIndex    0
@interface MoreUIViewController : UIViewController 
<UIActionSheetDelegate> {
    UITextField	*nameField;
    UITextField *numberField;
    UILabel     *sliderLabel;
    UISwitch	*leftSwitch;
    UISwitch	*rightSwitch;
    UIButton	*doRiskyThingButton;
}
    ...

Let's do some implementation for button's action, buttonPressed.

- (IBAction)buttonPressed {
    UIActionSheet *actionSheet = [[UIActionSheet alloc] 
                                  initWithTitle:@"Are you sure?" 
                                  delegate:self 
                                  cancelButtonTitle:@"No. Let's stop here!"
                                  destructiveButtonTitle:@"Yes, I'm Sure!" 
                                  otherButtonTitles:nil];
    [actionSheet showInView:self.view];
    [actionSheet release];    
}

Add following method, too.

- (void)actionSheet:(UIActionSheet *)actionSheet
didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex != [actionSheet cancelButtonIndex])
    {
        NSString *msg = nil;
        
        if (nameField.text.length > 0)
            msg = [[NSString alloc] initWithFormat:
                   @"You can breathe easy, %@, everything went OK."
                   , nameField.text];
        else
            msg = @"You can breathe easy, everything went OK.";
        
        UIAlertView *alert = [[UIAlertView alloc] 
                              initWithTitle:@"Something was done" 
                              message:msg 
                              delegate:self 
                              cancelButtonTitle:@"Phew!" 
                              otherButtonTitles:nil];
        [alert show];
        [alert release];
        [msg release];
    }
}

Final version of the two files are:

file "MoreUIViewController.h"

#import <UIKit/UIKit.h>
#define kSwitchesSegmentIndex    0
@interface MoreUIViewController : UIViewController 
<UIActionSheetDelegate> {
    UITextField	*nameField;
    UITextField *numberField;
    UILabel     *sliderLabel;
    UISwitch	*leftSwitch;
    UISwitch	*rightSwitch;
    UIButton	*doRiskyThingButton;
}
@property (nonatomic, retain) IBOutlet UITextField *nameField;
@property (nonatomic, retain) IBOutlet UITextField *numberField;
@property (nonatomic, retain) IBOutlet UILabel *sliderLabel;
@property (nonatomic, retain) IBOutlet UISwitch *leftSwitch;
@property (nonatomic, retain) IBOutlet UISwitch *rightSwitch;
@property (nonatomic, retain) IBOutlet UIButton	*doRiskyThingButton;
- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)backgroundTap:(id)sender;
- (IBAction)sliderChanged:(id)sender;
- (IBAction)toggleControls:(id)sender;
- (IBAction)switchChanged:(id)sender;
- (IBAction)buttonPressed;
@end

and for file "MoreUIViewController.m"

#import "MoreUIViewController.h"

@implementation MoreUIViewController
@synthesize nameField;
@synthesize numberField;
@synthesize sliderLabel;
@synthesize leftSwitch;
@synthesize rightSwitch;
@synthesize doRiskyThingButton;

- (IBAction)toggleControls:(id)sender {    
    if ([sender selectedSegmentIndex] == kSwitchesSegmentIndex)
    {
        leftSwitch.hidden = NO;
        rightSwitch.hidden = NO;
        doRiskyThingButton.hidden = YES;
    }
    else
    {
        leftSwitch.hidden = YES;
        rightSwitch.hidden = YES;
        doRiskyThingButton.hidden = NO;
    }
}

- (IBAction)switchChanged:(id)sender {
    UISwitch *whichSwitch = (UISwitch *)sender;
    BOOL setting = whichSwitch.isOn;
    [leftSwitch setOn:setting animated:YES];
    [rightSwitch setOn:setting animated:YES];
}

- (IBAction)buttonPressed {
    UIActionSheet *actionSheet = [[UIActionSheet alloc] 
                                  initWithTitle:@"Are you sure?" 
                                  delegate:self 
                                  cancelButtonTitle:@"No. Let's stop here!"
                                  destructiveButtonTitle:@"Yes, I'm Sure!" 
                                  otherButtonTitles:nil];
    [actionSheet showInView:self.view];
    [actionSheet release];    
}

- (IBAction)sliderChanged:(id)sender {
    UISlider *slider = (UISlider *)sender;
    int progressAsInt = (int)(slider.value + 0.5f);
    NSString *newText = [[NSString alloc] initWithFormat:@"%d", 
						 progressAsInt];
    sliderLabel.text = newText;
    [newText release];
}

- (IBAction)textFieldDoneEditing:(id)sender {
    [sender resignFirstResponder];
}

- (IBAction)backgroundTap:(id)sender {
    [nameField resignFirstResponder];
    [numberField resignFirstResponder];	
}

- (void)actionSheet:(UIActionSheet *)actionSheet
didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex != [actionSheet cancelButtonIndex])
    {
        NSString *msg = nil;
        
        if (nameField.text.length > 0)
            msg = [[NSString alloc] initWithFormat:
                   @"You can breathe easy, %@, everything went OK."
                   , nameField.text];
        else
            msg = @"You can breathe easy, everything went OK.";
        
        UIAlertView *alert = [[UIAlertView alloc] 
                              initWithTitle:@"Something was done" 
                              message:msg 
                              delegate:self 
                              cancelButtonTitle:@"Phew!" 
                              otherButtonTitles:nil];
        [alert show];
        [alert release];
        [msg release];
    }
}

- (void)viewDidLoad
{
    UIImage *buttonImageNormal = [UIImage imageNamed:@"whiteButton.png"];
    UIImage *stretchableButtonImageNormal = [buttonImageNormal 
                                             stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    [doRiskyThingButton setBackgroundImage:stretchableButtonImageNormal 
                                 forState:UIControlStateNormal];
    
    UIImage *buttonImagePressed = [UIImage imageNamed:@"blueButton.png"];
    UIImage *stretchableButtonImagePressed = [buttonImagePressed
                                              stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    [doRiskyThingButton setBackgroundImage:stretchableButtonImagePressed 
                                 forState:UIControlStateHighlighted];
    
}

- (void)viewDidUnload {
    self.nameField = nil;
    self.numberField = nil;
    self.sliderLabel = nil;
    self.leftSwitch = nil;
    self.rightSwitch = nil;
    self.doRiskyThingButton = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [nameField release];
    [numberField release];	
    [sliderLabel release];
    [leftSwitch release];
    [rightSwitch release];
    [doRiskyThingButton release];
    [super dealloc];
}

@end

Go to Xcode, Build and Run.

Done!