1

I have got a UITextView inside a UITableViewCell. As the user edits the UITextView text, I resize the UITextView and I need to resize the UITableViewCell and also make sure the UITableViewCell is always above the keyboard.

To resize the UITableViewCell without dismissing the keyboard I am doing the following and it works just fine:

[tableView beginUpdates];
[tableView endUpdates];

To move the tableView up and have the cell visible above the keyboard I am doing the following and it works just fine:

[tableView setContentOffset:CGPointMake(0, myDesiredScrollOffset) animated:YES];

My problem is that I cannot both have the UITableViewCell resized and moved smoothly together. Something like this will not work:

[tableView beginUpdates];
[tableView endUpdates];
[tableView setContentOffset:CGPointMake(0, myDesiredScrollOffset) animated:YES];

If I do it, the cell gets properly resized but the tableView scrolls all the way down instead. I understand that this way of resizing the UITableViewCell works asynchronously and that is why calling setContentOffset will not work, probably because the setContentOffset is being handled in the middle of the table view cell resizing handling process by iOS.

I than tried the following and the tableview ends up scrolling to the desired place. However the user can see the tableView go up and down and it looks strange:

[tableView beginUpdates];
[tableView endUpdates];
[self performSelector:@selector(myMethodToScroll) withObject:nil afterDelay:0.01];

...

 - (void) myMethodToScroll {
[self.tableView setContentOffset:CGPointMake(0, self.myDesiredScrollOffset) animated:YES];}

I would greatly appreciate help of this community on my problem because I have been struggling with this problem for a few days already. These tests are being made on devices with iOS 10 and iOS 11 and the results are the same.

Thanks in advance.

Mig70
  • 61
  • 10

2 Answers2

2

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.

Mig70
  • 61
  • 10
0

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

}
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • I am sorry... what is this self.tableViewBottomcon.constant ? – Mig70 Dec 20 '17 at 12:24
  • the bottom constraint of the tableView drag it as IBOutlet – Shehata Gamal Dec 20 '17 at 12:25
  • My tableview was created programatically. I believe an IBOutlet will not work here... – Mig70 Dec 20 '17 at 12:28
  • My problem is not setting the content offset. My problem is resizing the cell as the user type and at the same time do the offset. Please kindly read my post. – Mig70 Dec 20 '17 at 12:35
  • try dynamic cell height – Shehata Gamal Dec 20 '17 at 12:37
  • Would it be possible for you to send me a very simple example on how to do that? I currently have no experience on working with autoLayout. I believe I need to set a constraint. Suppose my uitableViewCell only has a uiview inside its contentView. How would I do that? – Mig70 Dec 20 '17 at 16:43