12
votes

I have a UITableView with a cell that is dynamically sized to fit a UITextView inside it. Whenever a key is typed, the cell checks to see if the calculated height has increased, as with a newline, so it can tell the table the the cell's height needs to be recalculated. I do that with this code.

- (void) createNoteCellViewDidUpdate: (CreateNoteCell*) cell {

    CGFloat newHeight = [self tableView:self.tableView heightForRowAtIndexPath:CreateNoteCellIndexPath];
    if (newHeight != CGRectGetHeight(cell.contentView.frame)) {
        [self.tableView beginUpdates];
        [self.tableView endUpdates]; // <- HERE IS WHERE THE CONTENT OFFSET CHANGES
    }
}

The resize and table bounds are as expected. But instead of just animating the change in height, it also scrolls to the top. This can be seen here, when pressing the return key.

table scrolling on cell resize

By Key Value Observing, I've found out that the scroll view contentOffset is being set when the scroll view's contentSize changes. And that the contentSize changes many times when the endUpdates method is called. When it changes it goes from a normal height, to a rather large height, then back to a normal height. I wonder if it's going to top because of that. I don't know where to go from here.

4
Question - why are you trying to resize your tableview instead of just resizing the cell? Normally the frame of a tableview stays constant and it's content changes.Frankie
No, I'm not touching the Table View frame or bounds. I'm resizing the cell by telling the table view to recalculate all the heights of the cells.Jadar

4 Answers

24
votes

Wow, Josh Gafni's answer helped a lot; he deserves an upvote. My solution is based off his. This still feels like a hack, so if anyone still has a better way to do this, I'm still very interested to hear it.

First I save the content offset, then I start the update.

CGPoint offset = self.tableView.contentOffset;
[self.tableView beginUpdates];
[self.tableView endUpdates];

This animates the cell's height change and adjusts all the frames to suit the constraints.

Next I remove all animations from the tableView's layer, so the animation to the top stops. Then I reset the contentOffst to what it was before.

[self.tableView.layer removeAllAnimations];
[self.tableView setContentOffset:offset animated:NO];

Finally, I let the tableView handle the animated scroll down so the bottom of the cell is right above the keyboard.

[self.tableView scrollToRowAtIndexPath:CreateNoteCellIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

And voila, it works pretty well.

fixed cell resize

10
votes

Here is my UITableView extension that works perfectly. In Swift 2.1. Based on the accepted answer.

extension UITableView
{
    func reloadDataAnimatedKeepingOffset()
    {
        let offset = contentOffset
        UIView.setAnimationsEnabled(false)
        beginUpdates()
        endUpdates()
        UIView.setAnimationsEnabled(true)
        layoutIfNeeded()
        contentOffset = offset
    }
}
8
votes

Just a guess...

Save the contentOffset of the uitableview before any changes. Then set it again after.

CGFloat contentOffset = self.tableView.contentOffset;

and then after the changes

self.tableView.contentOffset = contentOffset;
4
votes

More simplified solution (add Notification observer when textView is changed), Swift-version. Main trick is using UIView.setAnimationsEnabled.

func textViewDidChanged(notification: NSNotification) {
    UIView.setAnimationsEnabled(false)

    let contentOffset = self.tableView.contentOffset

    tableView.beginUpdates()
    tableView.endUpdates()

    tableView.contentOffset = contentOffset

    UIView.setAnimationsEnabled(true)
}