Matt Matt - 3 months ago 12
iOS Question

Generic UITableView keyboard resizing algorithm

I've searched a lot for code that resizes the table view to accomodate for keyboard showing and hiding, but almost every single post i came across assumes that the table view is taking the entire view of its view controller. I have an iPad application where the table view is only taking part of the screen. What's the correct way to resize the table view in this case? (all the code in the posts i've mentioned above fails)

Answer

The following code does what you want and works with any device and any layout. The code is courtesy of the Sensible TableView framework (with permission to copy and use).

- (void)keyboardWillShow:(NSNotification *)aNotification
{
if(keyboardShown) 
    return;

keyboardShown = YES;

// Get the keyboard size
UIScrollView *tableView;
if([self.tableView.superview isKindOfClass:[UIScrollView class]])
    tableView = (UIScrollView *)self.tableView.superview;
else
    tableView = self.tableView;
NSDictionary *userInfo = [aNotification userInfo];
NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [tableView.superview convertRect:[aValue CGRectValue] fromView:nil];

// Get the keyboard's animation details
NSTimeInterval animationDuration;
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
UIViewAnimationCurve animationCurve;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];

// Determine how much overlap exists between tableView and the keyboard
CGRect tableFrame = tableView.frame;
CGFloat tableLowerYCoord = tableFrame.origin.y + tableFrame.size.height;
keyboardOverlap = tableLowerYCoord - keyboardRect.origin.y;
if(self.inputAccessoryView && keyboardOverlap>0)
{
    CGFloat accessoryHeight = self.inputAccessoryView.frame.size.height;
    keyboardOverlap -= accessoryHeight;

    tableView.contentInset = UIEdgeInsetsMake(0, 0, accessoryHeight, 0);
    tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, accessoryHeight, 0);
}

if(keyboardOverlap < 0)
    keyboardOverlap = 0;

if(keyboardOverlap != 0)
{
    tableFrame.size.height -= keyboardOverlap;

    NSTimeInterval delay = 0;
    if(keyboardRect.size.height)
    {
        delay = (1 - keyboardOverlap/keyboardRect.size.height)*animationDuration;
        animationDuration = animationDuration * keyboardOverlap/keyboardRect.size.height;
    }

    [UIView animateWithDuration:animationDuration delay:delay 
                        options:UIViewAnimationOptionBeginFromCurrentState 
                     animations:^{ tableView.frame = tableFrame; } 
                     completion:^(BOOL finished){ [self tableAnimationEnded:nil finished:nil contextInfo:nil]; }];
}
}

- (void)keyboardWillHide:(NSNotification *)aNotification
{
if(!keyboardShown)
    return;

keyboardShown = NO;

UIScrollView *tableView;
if([self.tableView.superview isKindOfClass:[UIScrollView class]])
    tableView = (UIScrollView *)self.tableView.superview;
else
    tableView = self.tableView;
if(self.inputAccessoryView)
{
    tableView.contentInset = UIEdgeInsetsZero;
    tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
}

if(keyboardOverlap == 0)
    return;

// Get the size & animation details of the keyboard
NSDictionary *userInfo = [aNotification userInfo];
NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [tableView.superview convertRect:[aValue CGRectValue] fromView:nil];

NSTimeInterval animationDuration;
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
UIViewAnimationCurve animationCurve;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];

CGRect tableFrame = tableView.frame; 
tableFrame.size.height += keyboardOverlap;

if(keyboardRect.size.height)
    animationDuration = animationDuration * keyboardOverlap/keyboardRect.size.height;

[UIView animateWithDuration:animationDuration delay:0 
                    options:UIViewAnimationOptionBeginFromCurrentState 
                 animations:^{ tableView.frame = tableFrame; } 
                 completion:nil];
}

- (void) tableAnimationEnded:(NSString*)animationID finished:(NSNumber *)finished contextInfo:(void *)context
{
// Scroll to the active cell
if(self.activeCellIndexPath)
{
    [self.tableView scrollToRowAtIndexPath:self.activeCellIndexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
    [self.tableView selectRowAtIndexPath:self.activeCellIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}

Notes:

a. The above two methods have been added to the notification center using the following code:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];

b. The ivars used above has been declared like this:

BOOL keyboardShown;
CGFloat keyboardOverlap;

c. 'self.activeCellIndexPath' is always set to the indexPath of the cell owning the currently active UITextField/UITextView.

Enjoy! :)

Comments