I managed to figure out how to do it! There are 3 very important details:
- Resize a cell without closing the keyboard. (A regular reboot will hide the keyboard).
- Cell height adjustment is performed only after the content shift animation is completed. (Otherwise, the setContentOffset parameter may be ignored).
- Set the bottom insert for tableView. (Otherwise, the cell size setting may scroll down the table, hiding the cell that we want to see above the keyboard.)
This solution was tested on iOS10 and iOS11 with real iPhones.
Here's how (all the code is implemented in 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 is to adjust the cell size only after the end of the scroll animation:
- (void) setContentOffsetAndAdjustCellSizes:(CGFloat)contentOffset{ [UIView animateWithDuration:0.5 animations:^ { [self.tableView setContentOffset:CGPointMake(0, contentOffset) animated:NO]; } completion:^(BOOL finished) { [self.tableView beginUpdates];
Very important: after closing the keyboard, smoothly drag the table 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 it all 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 can also scan his editing directly from one text field or textView in one cell to another in another cell without closing the keyboard. This must be taken into account and processed.
The scroll method should also be called whenever the textView text changes, because in case of its size and, therefore, the cell size also needs to be changed:
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];
Also useful:
typedef NS_ENUM(NSUInteger, TableViewScrollDirection){ TableViewScrollNone, TableViewScrollDown, TableViewScrollUp };
Useful property to create in viewController:
@property (nonatomic) BOOL textControlCellHadToMoveUpToBeVisibleOverKeyboard;
I hope this will be helpful.