The Cocoa Touch framework offers a number of UI elements, ranging
from text entry fields to switches and segmented controls. Any of these can be used for data entry, but often when we
talk about data entry we’re talking about getting textual information
into an application.
The two main UI elements that allow you to enter text are the
UITextField and UITextView classes. While they may sound similar, they are actually quite
different. The most noticeable difference between the two is that the
UITextView allows you to enter (and
display) a multiline text field, while UITextField doesn’t.
The most annoying difference between the two is the issue of the
resigning first responder. When tapped,
both display a keyboard to allow the user to enter text. However, while
the UITextField class allows the user
to dismiss the keyboard (at which time the text field resigns as first
responder) when the user taps the Done button, the UITextView class does not. Though there are
multiple ways around this problem, as we’ll find later on, it’s still one
of the more annoying quirks in the Cocoa Touch framework.
1. UITextField and Its Delegate
We
were simply polling the text field to see if the user had entered any
text when the Save button was tapped, and perhaps more important, we
weren’t dismissing the keyboard when the user pressed the Return key.
Here’s the saveCity:sender method
from that example:
- (void)saveCity:(id)sender {
CityGuideDelegate *delegate =
(CityGuideDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableArray *cities = delegate.cities;
UITextField *nameEntry = (UITextField *)[nameCell viewWithTag:777];
UITextView *descriptionEntry =
(UITextView *)[descriptionCell viewWithTag:777];
if ( nameEntry.text.length > 0 ) {
City *newCity = [[City alloc] init];
newCity.cityName = nameEntry.text;
newCity.cityDescription = descriptionEntry.text;
newCity.cityPicture = cityPicture;
[cities addObject:newCity];
RootController *viewController = delegate.viewController;
[viewController.tableView reloadData];
}
[delegate.navController popViewControllerAnimated:YES];
}
However, the UITextFieldDelegate
protocol offers a rich set of delegate methods. To use
them, you must declare your class as implementing that delegate protocol
(lines with changes are shown in bold):
@interface AddCityController : UIViewController
<UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>
{
UITextField *activeTextField;
... remainder of example code not shown ...
}
Note:
After implementing the delegate protocol, open the NIB that
contains the UITextField
(AddCityController.xib in the case of CityGuide). Next, Ctrl-drag from the
UITextField to the controller
(File’s Owner in AddCityController.xib) and
select delegates from the pop up that appears. Save the NIB when
you’re done.
When the user taps the text field, the textFieldShouldBeginEditing:
method is called in the delegate to ascertain whether the
text field should enter edit mode and become the first responder. To
implement this, you’d add the following to your controller’s implementation (such as
AddCityController.m):
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
activeTextField = textField;
return YES;
}
If this method returns NO, the
text field will not become editable. Only if this method returns
YES will the text field enter edit
mode. At this point, the keyboard will be presented to the user; the
text field will become the first responder; and the textFieldDidBeginEditing: delegate method will
be called.
The easiest way to hide the keyboard is to implement the
textFieldShouldReturn:
delegate method and explicitly
resign as the first responder. This method is called in the delegate when the Return key
on the keyboard is pressed. To dismiss the text field when you tapped on
the Return button, you’d add the following to your controller’s implementation:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
activeTextField = nil;
[textField resignFirstResponder];
return YES;
}
This method is usually used to make the text field resign as first
responder, at which point the delegate methods textFieldShouldEndEditing:
and textFieldDidEndEditing: will be
triggered.
These methods can be used to update the data model with new
content if required, or after parsing the input, to make other
appropriate changes to the UI such as adding or removing additional
elements.
2. UITextView and Its Delegate
2.1. Dismissing the UITextView
The UITextViewDelegate
protocol lacks the equivalent to the textFieldShouldReturn: method, presumably
since we shouldn’t expect the Return key to be a signal that the user
wishes to stop editing the text in a multiline text entry dialog
(after all, the user may want to insert line breaks by pressing
Return).
However, there are several ways around the inability of the
UITextView to resign as first
responder using the keyboard. The usual method is to place a Done
button in the navigation bar when the UITextView presents the pop-up keyboard.
When tapped, this button asks the text view to resign as first
responder, which will then dismiss the keyboard.
However, depending on how you’ve planned out your interface, you
might want the UITextView to resign
when the user taps outside the UITextView itself.
To do this, you’d subclass UIView to accept touches, and then instruct
the text view to resign when the user taps outside the view itself.
Right-click on the Classes group in the Groups & Files pane in the
Xcode interface, select Add→New File,
and choose Cocoa Touch Class from the iPhone OS section. Next, select
“Objective-C class” and choose UIView from the “Subclass of” menu.
Click Next and name the class “CustomView”.
In the interface (CustomView.h), add an
IBOutlet for a UITextView:
#import <UIKit/UIKit.h>
@interface CustomView : UIView {
IBOutlet UITextView *textView;
}
@end
Then, in the implementation (CustomView.m),
implement the touchesEnded:withEvent: method and ask the
UITextView to resign as first
responder. Here’s what the implementation should look like (added
lines are shown in bold):
#import "CustomView.h"
@implementation CustomView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}
- (void)dealloc {
[super dealloc];
}
- (void) awakeFromNib {
self.multipleTouchEnabled = YES;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"touches began count %d, %@", [touches count], touches);
[textView resignFirstResponder];
[self.nextResponder touchesEnded:touches withEvent:event];
}
@end
Once you’ve added the class, you need to save all your changes,
then go into Interface Builder and click on your view. Open the
Identity Inspector (⌘-4) and change the type of the view in your NIB
file to be your CustomView rather
than the default UIView class. Then
in the Connections Inspector (⌘-2), drag the textView outlet to the UITextView. After doing so, and once you
rebuild your application, touches outside the active UI elements will
now dismiss the keyboard.
While this solution is elegant, it can be used in only some
situations. In many cases, you’ll have to resort to the brute force
method of adding a Done button to the navigation bar to dismiss the
keyboard.