I managed to find out how to do it!
There are 3 very important details:
- The way cells are resized without closing the keyboard. (A normal reload will hide the keyboard).
- Setting the cells height only be done after the content offset animation completes. (Otherwise the setContentOffset may be ignored).
- 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.