Generic UITableView keyboard resizing algorithm

2020-02-23 02:05发布

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)

7条回答
Melony?
2楼-- · 2020-02-23 02:16

I've found the easiest solution to be this one(I'm not fan of using subviews for this kind of stuff):

register for keyboard frame change notification(idealy register in viewWillAppear: and unregister in viewWillDisappear:):

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];

and then in method:

- (void)keyboardDidChangeFrame:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
    CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect kbIntersectFrame = [window convertRect:CGRectIntersection(window.frame, kbFrame) toView:self.scrollView];
    kbIntersectFrame = CGRectIntersection(self.bounds, kbIntersectFrame);

    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbIntersectFrame.size.height, 0.0);
    self.scrollView.contentInset = contentInsets;
    self.scrollView.scrollIndicatorInsets = contentInsets;
}

or if You want to get rid of the "jump" after changing contentInset:

- (void)keyboardDidChangeFrame:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
    CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect kbIntersectFrame = [window convertRect:CGRectIntersection(window.frame, kbFrame) toView:self.scrollView];
    kbIntersectFrame = CGRectIntersection(self.scrollView.bounds, kbIntersectFrame);

    // get point before contentInset change
    CGPoint pointBefore = self.scrollView.contentOffset;
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbIntersectFrame.size.height, 0.0);
    self.scrollView.contentInset = contentInsets;
    self.scrollView.scrollIndicatorInsets = contentInsets;
    // get point after contentInset change
    CGPoint pointAfter = self.scrollView.contentOffset;
    // avoid jump by settings contentOffset
    self.scrollView.contentOffset = pointBefore;
    // and now animate smoothly
    [self.scrollView setContentOffset:pointAfter animated:YES];
}
查看更多
Viruses.
3楼-- · 2020-02-23 02:16

Here is the keyboard method:

func keyboardControl(notification: NSNotification, isShowing: Bool) {
    var userInfo = notification.userInfo!
    let keyboardRect = userInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue
    let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey]!.unsignedIntValue

    let convertedFrame = self.view.convertRect(keyboardRect, fromView: nil)
    let heightOffset = self.view.bounds.size.height - convertedFrame.origin.y
    let options = UIViewAnimationOptions(rawValue: UInt(curve) << 16)
    let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey]!.doubleValue

    //your UITableView bottom constrant
    self.tableViewMarginBottomConstraint.constant = heightOffset

    var contentInsets = UIEdgeInsetsZero
    if isShowing {
        contentInsets = UIEdgeInsetsMake(0.0, 0.0, (keyboardRect.size.height), 0.0)
    }

    UIView.animateWithDuration(
        duration,
        delay: 0,
        options: options.union(.LayoutSubviews).union(.BeginFromCurrentState),
        animations: {
            self.listTableView.contentInset = contentInsets
            self.listTableView.scrollIndicatorInsets = contentInsets
            self.listTableView.scrollBottomToLastRow()
            self.view.layoutIfNeeded()
        },
        completion: { bool in
    })
}

Here is the UITableView extension:

extension UITableView {
    func totalRows() -> Int {
        var i = 0
        var rowCount = 0
        while i < self.numberOfSections {
            rowCount += self.numberOfRowsInSection(i)
            i++
        }
        return rowCount
    }

    var lastIndexPath: NSIndexPath {
        get { return NSIndexPath(forRow: self.totalRows()-1, inSection: 0) }
    }

    func scrollBottomToLastRow() {
        self.scrollToRowAtIndexPath(self.lastIndexPath, atScrollPosition: .Bottom, animated: false)
    }
}
查看更多
你好瞎i
4楼-- · 2020-02-23 02:17

Simple solution - register to receive keyboard notifications on init or viewDidLoad with:

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

then when the notification is received, you can use the given rect of the keyboard to adjust the frame of your tableView:

- (void)keyboardWillShow:(NSNotification *)notification
{
   // Get the size of the keyboard.
   CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

   CGRect newTableFrame = _myTableView.frame;
   //Here make adjustments to the tableview frame based on the value in keyboard size
   ...

   _myTableView.frame = newTableFrame;
}

- (void)keyboardWillHide:(NSNotification *)notification
{
   //Here change the table frame back to what it originally was.
}
查看更多
够拽才男人
5楼-- · 2020-02-23 02:21

You can achieve what you're looking for by using IQKeyboardManager, it's a codeless library, you just have to add it to your Podfile: pod 'IQKeyboardManager' and that's it, it will hand the scrolling effect when the keyboard is shown even if the UITextField/UITextView is not part of a scrollView/tableView.

查看更多
一纸荒年 Trace。
6楼-- · 2020-02-23 02:27

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! :)

查看更多
做自己的国王
7楼-- · 2020-02-23 02:32

The simple solution is to add my extension UIViewController+Keyboard.swift to your project, with a single line setupKeyboardNotifcationListenerForScrollView(tableView) it will auto resize automatically. No need to subclass anything, just an extension! Its open source at SingleLineKeyboardResize

查看更多
登录 后发表回答