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.
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".
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.
While the image view selected as in the picture, open Inspector Window under Tools of Interface Builder.
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.
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.
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.
Name field
Number field
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.
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.
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.
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.
Select the View and open up identity inspector from Tools menu. From the picture below, change the field labeled Class to UIControl from UIView.
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.
Compare that with this picture which is before the class change.
And also look at the MoreUIViewControl.xib. The name has been changed to UIControl from UIView as we expected.
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.
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.
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.
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.
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.
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.
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.
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
#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!