Why do you want to create a table full of textfields? You should be using a detailed view for each row that contains the text fields.
When you push your detailedview, ensure that you call "[myTextField becomeFirstResponder]" so that the user can start editing with just one click away from the table list.
If you're willing to subclass (ugh!) your table view, something like this might work:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
BOOL backgroundTouched = YES;
for (UITouch *touch in touches) {
CGPoint location = [touch locationInView:self];
for (UITableViewCell *cell in self.visibleCells) {
if (CGRectContainsPoint(cell.frame, location)) {
backgroundTouched = NO;
break;
}
}
}
if (backgroundTouched) {
for (UITableViewCell *cell in self.visibleCells) {
// This presumes the first subview is the text field you want to resign.
[[cell.contentView.subviews objectAtIndex:0] resignFirstResponder];
}
}
[super touchesBegan:touches withEvent:event];
}
Then make sure that in your Nib file you set the type of your UITableView to DismissableUITableView .....maybe i could have thought of a better name for this class, but you get the point.
The way I did it was to listen for a scroll event by the user and then resignFirstResponder. Here's the UIScrollViewDelegate method to implement in your code;
When approaching these sorts of problems I've found the best way is to research the delegate protocols for each object and those of the parent classes (in this case UITableViewDelegate, UIScrollViewDelegate. The number of events NS objects fires is quite large and comprehensive. It's also easier implementing a protocol then subclassing anything.
This is easily done by creating a UITapGestureRecognizer object (by default this will detect a "gesture" on a single tap so no further customization is required), specifying a target/action for when the gesture is fired, and then attaching the gesture recognizer object to your table view.
Note that the gesture is not fired when touching inside a UITextField object. It is fired though on the UITableView background, footer view, header view and on UILabels inside cells etc.
Some textfields might have a picker view or some other as a subview,so in that case the above method doesn't work so in that case we need to make use of UITapGestureRecognizer class i.e. add the following code snippet to viewDidLoad method i.e.:
As UITableView is a subclass of UIScrollView, implementing one delegate method below provides an extremely easy, quick solution. No need to even involve resignFirstResponder since view hierarchy introspects and finds the current responder and asks it to resign it's responder status.
(NOTE: I've subclassed the cell but you can easily achieve this in the tableView:didSelectRowAtIndexPath: delegate method of UITableView)
Doing this meant that with the top solutions if you clicking on the cell twice the keyboard would shake as, first the gesture recogniser tried to close the keyboard, and second the cell was reselected and tried open the keyboard.
Solution is to check whether the click occurred inside the currently selected cell:
- (void)viewDidLoad
{
[super viewDidLoad];
//gesture recognizer to close the keyboard when user taps away
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(dismissKeyboard:)];
tap.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:tap];
}
-(void)dismissKeyboard:(UIGestureRecognizer*)tapGestureRecognizer
{
if (!CGRectContainsPoint([self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]].frame, [tapGestureRecognizer locationInView:self.tableView]))
{
[self.view endEditing:YES];
}
}
Many interesting answers. I would like to compile different approaches into the solution that i thought best fit a UITableView scenario (it's the one I usually use):
What we usually want is basically to hide the keyboard on two scenarios: on tapping outside of the Text UI elements, or on scrolling down/up the UITableView. The first scenario we can easily add via a TapGestureRecognizer, and the second via the UIScrollViewDelegate scrollViewWillBeginDragging: method.
First order of business, the method to hide the keyboard:
/**
* Shortcut for resigning all responders and pull-back the keyboard
*/
-(void)hideKeyboard
{
//this convenience method on UITableView sends a nested message to all subviews, and they resign responders if they have hold of the keyboard
[self.tableView endEditing:YES];
}
This method resigns any textField UI of the subviews within the UITableView view hierarchy, so it's more practical than resigning every single element independently.
Next we take care of dismissing via an outside Tap gesture, with:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupKeyboardDismissGestures];
}
- (void)setupKeyboardDismissGestures
{
// Example for a swipe gesture recognizer. it was not set-up since we use scrollViewDelegate for dissmin-on-swiping, but it could be useful to keep in mind for views that do not inherit from UIScrollView
// UISwipeGestureRecognizer *swipeUpGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(hideKeyboard)];
// swipeUpGestureRecognizer.cancelsTouchesInView = NO;
// swipeUpGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
// [self.tableView addGestureRecognizer:swipeUpGestureRecognizer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideKeyboard)];
//this prevents the gestureRecognizer to override other Taps, such as Cell Selection
tapGestureRecognizer.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:tapGestureRecognizer];
}
Setting tapGestureRecognizer.cancelsTouchesInView to NO is to avoid the gestureRecognizer from overriding the normal inner workings of the UITableView (for example, not to interfere with cell Selection).
Finally, to handle hiding the keyboard on Scrolling up/down the UITableView, we must implement the UIScrollViewDelegate protocol scrollViewWillBeginDragging: method, as:
Simply using a UITapGestureRecognizer and cancelsTouchesInView = NO means that taps on cells and UITextViews also trigger the hide. This is bad if you have multiple UITextViews and you tap on the next one. The keyboard will start to hide and then the next textView becomes the firstResponder and the keyboard becomes visible again. To avoid this, check the tap location and only hide the keyboard if the tap isn't on a cell:
Here's how I finally made works. I combined suggestions and codes from different answers.
Features: dismissing keyboard, moving text fields above keyboard while editing and setting "Next" and "Done" keyboard return type.REPLACE "..." with more fields
static const CGFloat ANIMATION_DURATION = 0.4;
static const CGFloat LITTLE_SPACE = 5;
CGFloat animatedDistance;
CGSize keyboardSize;
@interface ViewController () <UITextFieldDelegate>
@property (weak, nonatomic) IBOutlet UITextField *firstNameTXT;
.....// some other text fields
@property (weak, nonatomic) IBOutlet UITextField *emailTXT;
@end
@implementation ViewController
- (void)viewDidLoad{
.....
// add tap gesture to help in dismissing keyboard
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:@selector(tapScreen:)];// outside textfields
[self.view addGestureRecognizer:tapGesture];
// set text fields return key type to Next, last text field to Done
[self.firstNameTXT setReturnKeyType:UIReturnKeyNext];
.....
[self.emailTXT setReturnKeyType:UIReturnKeyDone];
// set text fields tags
[self.firstNameTXT setTag:0];
....// more text fields
[self.emailTXT setTag:5];
// add keyboard notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
}
// dismiss keyboard when tap outside text fields
- (IBAction)tapScreen:(UITapGestureRecognizer *)sender {
if([self.firstNameTXT isFirstResponder])[self.firstNameTXT resignFirstResponder];
...
if([self.emailTXT isFirstResponder])[self.emailTXT resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
if(textField.returnKeyType==UIReturnKeyNext) {
// find the text field with next tag
UIView *next = [[textField superview] viewWithTag:textField.tag+1];
[next becomeFirstResponder];
} else if (textField.returnKeyType==UIReturnKeyDone || textField.returnKeyType==UIReturnKeyDefault) {
[textField resignFirstResponder];
}
return YES;
}
// Moving current text field above keyboard
-(BOOL) textFieldShouldBeginEditing:(UITextField*)textField{
CGRect viewFrame = self.view.frame;
CGRect textFieldRect = [self.view.window convertRect:textField.bounds fromView:textField];
CGRect viewRect = [self.view.window convertRect:self.view.bounds fromView:self.view];
CGFloat textFieldBottomLine = textFieldRect.origin.y + textFieldRect.size.height + LITTLE_SPACE;//
CGFloat keyboardHeight = keyboardSize.height;
BOOL isTextFieldHidden = textFieldBottomLine > (viewRect.size.height - keyboardHeight)? TRUE :FALSE;
if (isTextFieldHidden) {
animatedDistance = textFieldBottomLine - (viewRect.size.height - keyboardHeight) ;
viewFrame.origin.y -= animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
return YES;
}
-(void) restoreViewFrameOrigionYToZero{
CGRect viewFrame = self.view.frame;
if (viewFrame.origin.y != 0) {
viewFrame.origin.y = 0;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:ANIMATION_DURATION];
[self.view setFrame:viewFrame];
[UIView commitAnimations];
}
}
-(void)keyboardDidShow:(NSNotification*)aNotification{
NSDictionary* info = [aNotification userInfo];
keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
}
-(void)keyboardDidHide:(NSNotification*)aNotification{
[self restoreViewFrameOrigionYToZero];// keyboard is dismissed, restore frame view to its zero origin
}
@end
Then, implement the shouldReceiveTouch delegate method to reject touches that are performed in UITableViewCell class. The hideKeyboard method only will be called when the touch has been performed outside UITableViewCell class.
@mixca's answer is very useful but what if i've something different from UITextField. I think best way to handle it by searching all subviews of main view with recursive function, check example below
- (BOOL)findAndResignFirstResponder {
if (self.isFirstResponder) {
[self resignFirstResponder];
return YES;
}
for (UIView *subView in self.subviews) {
if ([subView findAndResignFirstResponder]) {
return YES;
}
}
return NO;
}
and also you can put this method to your utility class and can use from tap gesture like @mixca's answer..
//Calls this function when the tap is recognized.
@objc func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}