Dec 192011
 

This is a pretty classic first iPhone app problem. You have a UITableView with some cells and some of the cells have UITextFields and when you tap the text field the keyboard slides out of the bottom and your textfield is hidden.

There are scores of blogs and tutorials on how to fix this and I thought I’d share my all purpose code for this. It works on both UITableView views as well as views not in a table.

First, the code dump:

+ (void)keyboardWasShown:(NSNotification*)aNotification
                    view:(UIViewController *) view
              scrollView:(UIScrollView *)scrollView
             activeField:(UIView *) activeField
              activeCell:(UITableViewCell *) activeCell
{
    NSDictionary* info = [aNotification userInfo];
    CGRect rawKeyboardRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
    
    CGRect properlyRotatedCoords = [view.view.window convertRect:rawKeyboardRect toView:view.view.window.rootViewController.view];
    
    CGSize kbSize = properlyRotatedCoords.size;
    
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
    scrollView.contentInset = contentInsets;
    scrollView.scrollIndicatorInsets = contentInsets;
    
    CGRect viewRect = view.view.frame;
    
    // If active text field is hidden by keyboard, scroll it so it's visible
    // do it for tables
    if (nil != activeCell) {
        viewRect.size.height -= kbSize.height;
        
        CGPoint origin = activeCell.frame.origin;
        origin.x += activeField.frame.origin.x;
        origin.y += activeField.frame.origin.y;
        origin.y -= scrollView.contentOffset.y;
        
        if (!CGRectContainsPoint(viewRect, origin) ) {
            CGFloat scrollAmount = activeCell.frame.origin.y + activeField.frame.origin.y - 30;          // the -30 is just to make it look better
            CGPoint scrollPoint = CGPointMake(0.0, scrollAmount);
            
            [scrollView setContentOffset:scrollPoint animated:YES];
        }

    } else {  // and do it when you are not in a table
        
        CGRect fieldRect = activeField.frame;
        
        float distanceFromBottom = (viewRect.origin.y + viewRect.size.height) - (fieldRect.origin.y + fieldRect.size.height);
        float jumpNeeded = kbSize.height - distanceFromBottom;
        
        // the -30 is just to make it look better
        [scrollView setContentOffset:CGPointMake(0,jumpNeeded +30) animated:YES];
    }
    
}

And the interface:

@interface EnkiUtilities : NSObject

+ (void)keyboardWasShown:(NSNotification*)aNotification 
                    view:(UIViewController *) view 
              scrollView:(UITableView *)scrollView 
             activeField:(UIView *) activeField
              activeCell:(UITableViewCell *) activeCell;
@end

Notice that it is a class method, not an instance method. I keep a class called EnkiUtilities around to hold useful things. To call it, we first set up our observer:

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];

And in the keyboardWasShown method we call our utilities method:

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    [EnkiUtilities keyboardWasShown:aNotification view:self scrollView:myTableView activeField:activeField activeCell:activeCell];

}

So what is activeField and activeCell? Lets look at our textViewDidBeginEditing method to see how those are set

-(void)textViewDidBeginEditing:(UITextView *)sender
{           
    activeField = sender;
    
    if ([sender isEqual:descriptionTextView]) {
        activeCell = descriptionCell;
        if (shouldClearDescription) {
            [descriptionTextView initWithLPLStyle:@""];
            shouldClearDescription = false;
        } 
    }else if ([sender isEqual:hintTextView]) {
        activeCell = hintCell;
        if (shouldClearHint) {
            [hintTextView initWithLPLStyle:@""];
            shouldClearHint = false;
        }    
    } else {
        activeCell = nil;
    }
}

This method is called when a UITextField begins editing. I set the activeField to the sender and then I set the activeCell to the cell that corresponds to the field.

The only bit remaining is to restore the view when the keyboard disappears.

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    myTableView.contentInset = contentInsets;
    myTableView.scrollIndicatorInsets = contentInsets;
}

When you are working without a UITableView, just put the text fields into a UIScrollView and pass that, not the UITableView, to keyboardWasShown.

This has been incorporated into the open source EnkiUtils.