Resize and move UITableViewCell smoothly without dismissing keyboard

岁酱吖の 提交于 2019-12-04 21:49:40

I managed to find out how to do it! There are 3 very important details:

  1. The way cells are resized without closing the keyboard. (A normal reload will hide the keyboard).
  2. Setting the cells height only be done after the content offset animation completes. (Otherwise the setContentOffset may be ignored).
  3. Set a bottom inset for the tableView. (Otherwise adjusting the cells size may scroll down the table, hiding the cell we want visible above the keyboard.)

This solution has been tested in iOS10 and iOS11 with real iPhones.

Here is how (all code is implemented in the viewController):

- (TableViewScrollDirection) scrollToKeepEditingCellVisibleAboveVerticalPoint:(CGFloat)verticalPoint cellIndexPath:(NSIndexPath*)cellIndexPath anchorsToVerticalPoint:(BOOL)anchorsToVerticalPoint cellHeight:(CGFloat)cellHeight adjustsCellSize:(BOOL)adjustsCellSize {
// Remark: verticalPoint is the desired offset above the tableView bottom. In my case the height of the keyboard covering the bottom of the tableView

CGRect cellFrame = CGRectOffset([self.tableView rectForRowAtIndexPath:cellIndexPath], -self.tableView.contentOffset.x, -self.tableView.contentOffset.y - self.tableView.contentInset.top);
CGFloat cellBottom = adjustsCellSize ? cellFrame.origin.y + cellHeight : cellFrame.origin.y + cellFrame.size.height;
CGFloat offsetNeeded = cellBottom - verticalPoint; // Relative offset
CGFloat brandNewOffset = self.tableView.contentOffset.y + offsetNeeded; // Absolute offset

if ((offsetNeeded > 0) || ((offsetNeeded < 0) && anchorsToVerticalPoint))
{
    CGFloat elasticity = self.tableView.frame.size.height - verticalPoint;
    if (self.tableView.contentInset.bottom != elasticity)
        self.tableView.contentInset = UIEdgeInsetsMake(0, 0, elasticity, 0); // This will make sure the tableview does not scroll down when its content offset elasticity is not enough

    if (adjustsCellSize)
        [self setContentOffsetAndAdjustCellSizes:brandNewOffset];
    else
        [self.tableView setContentOffset:CGPointMake(0, brandNewOffset) animated:YES];

    if (offsetNeeded > 0)
        return TableViewScrollUp;
    else if (offsetNeeded < 0)
        return TableViewScrollDown;
}

return TableViewScrollNone;}

The second part of the trick lays on adjusting the cell size only after the scroll animation ends:

- (void) setContentOffsetAndAdjustCellSizes:(CGFloat)contentOffset{
[UIView animateWithDuration:0.5 animations:^
{
    [self.tableView setContentOffset:CGPointMake(0, contentOffset) animated:NO];
}
completion:^(BOOL finished)
{
    [self.tableView beginUpdates]; // Cell height must be adjusted this way, otherwise the keyboard closes.
    [self.tableView endUpdates];
}];}

Very important: After the keyboard closes, smoothly readjust the tableview scroll (if necessary):

- (void) keyboardDidHide:(NSNotification *)notification {

if (self.tableView.contentInset.bottom != 0)
    [UIView animateWithDuration:0.5 animations:^ {self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);}];

self.activeKeyboardSize = CGSizeZero; }

How everything starts:

- (void) keyboardDidShow:(NSNotification*)notification {
NSDictionary* info = [notification userInfo];
self.activeKeyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;

CGFloat tableViewBottom = self.tableView.frame.origin.y + self.tableView.frame.size.height;
CGFloat keyboardTop = self.view.frame.size.height - self.activeKeyboardSize.height;
CGFloat coveringVerticalSpace = tableViewBottom - keyboardTop;
if (coveringVerticalSpace <= 0)
    return;

TableViewScrollDirection scrollDirection = [self scrollToKeepEditingCellVisibleAboveVerticalPoint:self.tableView.frame.size.height - coveringVerticalSpace - UI_MARGIN_DEFAULT anchorsToVerticalPoint:NO];
if (scrollDirection == TableViewScrollUp)
    self.textControlCellHadToMoveUpToBeVisibleOverKeyboard = YES;}

The user may also jump its editing directly from one textField or textView in one cell to another in another cell without closing the keyboard. This must be taken into account and handled.

The scrolling method should also be called whenever a textView text changes because in case its size and therefore the cell's size also need to change:

CGFloat tableViewBottom = self.tableView.frame.origin.y + self.tableView.frame.size.height;
CGFloat keyboardTop = self.view.frame.size.height - self.activeKeyboardSize.height;
CGFloat coveringVerticalSpace = tableViewBottom - keyboardTop;
if (coveringVerticalSpace <= 0)
    return;

[self scrollToKeepEditingCellVisibleAboveVerticalPoint:self.tableView.frame.size.height - coveringVerticalSpace - UI_MARGIN_DEFAULT anchorsToVerticalPoint:self.textControlCellHadToMoveUpToBeVisibleOverKeyboard cellHeight:staticCellView.frame.size.height adjustsCellSize:adjustsCellSize]; // UI_MARGIN_DEFAULT is 8.0 and it gives a little margin of 8 points over the keyboard.

Also useful:

typedef NS_ENUM(NSUInteger, TableViewScrollDirection){
TableViewScrollNone,
TableViewScrollDown,
TableViewScrollUp };

A useful property to create in the viewController:

@property (nonatomic) BOOL textControlCellHadToMoveUpToBeVisibleOverKeyboard;

I hope this will be of use.

hook you constraints properly in cell from top to bottom also don't implement heightForRowAtIndexpath

then

in viewDidLoad

tableView.estimatedRowHeight = 200;

tableView.rowHeight = UITableViewAutomaticDimension;

in data source

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

       let cell = tableView.dequeueReusableCell(withIdentifier:CellIdentifier1) as! logTableViewCell

       // your code here


    cell.layoutSubviews()

    cell.layoutIfNeeded()

    return cell

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!