iOS 8 Auto cell height - Can't scroll to last row

IosUitableviewSwiftIos8

Ios Problem Overview


I am using iOS 8 new self-sizing cells. Visually it works good - each cell gets its right size. However, if I try to scroll to the last row, the table view doesn't seem to know its right size. Is this a bug or is there a fix for that?

Here's how to recreate the problem:

Using this project - TableViewCellWithAutoLayoutiOS8 (referenced from this SO answer), I got the auto-resizing cells as expected.

However, if I am calling the scrollToRowAtIndexPath function, like this:

tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)

I do not get to the last row - It only gets me around halfway there.

Even by trying to use a lower level function like this:

tableView.setContentOffset(CGPointMake(0, tableView.contentSize.height - tableView.frame.size.height), animated: true)

The result is not as expected, it won't get to the end. If I click it a lot of times or wait a few moments, eventually it will get to the right place. It seems the tableView.contentSize.height is not set correctly, so the iOS "doesn't know" where that last cell is.

Would appreciate any help.

Thanks

Ios Solutions


Solution 1 - Ios

Update: Jun 24, 2015

Apple has addressed most of these bugs as of the iOS 9.0 SDK. All of the issues are fixed as of iOS 9 beta 2, including scrolling to the top & bottom of the table view without animation, and calling reloadData while scrolled in the middle of the table view.

Here are the remaining issues that have not been fixed yet:

  1. When using a large estimated row height, scrolling to the last row with animation causes the table view cells to disappear.
  2. When using a small estimated row height, scrolling to the last row with animation causes the table view to finish scrolling too early, leaving some cells below the visible area (and the last row still offscreen).

A new bug report (rdar://21539211) has been filed for these issues relating to scrolling with animation.

Original Answer

This is an Apple bug with the table view row height estimation, and it has existed since this functionality first was introduced in iOS 7. I have worked directly with Apple UIKit engineers and developer evangelists on this issue -- they have acknowledged that it is a bug, but do not have any reliable workaround (short of disabling row height estimation), and did not seem particularly interested in fixing it.

Note that the bug manifests itself in other ways, such as disappearing table view cells when you call reloadData while scrolled partially or fully down (e.g. contentOffset.y is significantly greater than 0).

Clearly, with iOS 8 self sizing cells, row height estimation is critically important, so Apple really needs to address this ASAP.

I filed this issue back on Oct 21 2013 as Radar #15283329. Please do file duplicate bug reports so that Apple prioritizes a fix.

You can attach this simple sample project to demonstrate the issue. It is based directly on Apple's own sample code.

Solution 2 - Ios

This has been a very annoying bug, but I think I found a permanent solution, though I cannot fully explain why.

Call the function after a tiny (unnoticed) delay:

let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
        
dispatch_after(time, dispatch_get_main_queue(), {
  tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)
})

Do tell me if this works for you as well.

Solution 3 - Ios

It is definitely a bug from Apple. I also have this problem. I solved this problem by calling "scrollToRowAtIndexPath" method twice example code is:

        if array.count > 0 {
        let indexPath: NSIndexPath = NSIndexPath(forRow: array.count - 1, inSection: 0)
        self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
        let delay = 0.1 * Double(NSEC_PER_SEC)
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
        
        dispatch_after(time, dispatch_get_main_queue(), {
            self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
        })
    }

Solution 4 - Ios

I found a temporary workaround that might be helpful until Apple decides to fixes the many bugs that have been plaguing us.

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *text = [self findTextForIndexPath:indexPath];
    UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:13];
    CGRect estimatedHeight = [text boundingRectWithSize:CGSizeMake(215, MAXFLOAT)
                                                options:NSStringDrawingUsesLineFragmentOrigin
                                             attributes:@{NSFontAttributeName: font}
                                                context:nil];
    return TOP_PADDING + CGRectGetHeight(estimatedHeight) + BOTTOM_PADDING;
}

This is not perfect, but it did the job for me. Now I can call:

- (void)scrollToLastestSeenMessageAnimated:(BOOL)animated
{
    NSInteger count = [self tableView:self.tableView numberOfRowsInSection:0];
    if (count > 0) {
        NSInteger lastPos = MAX(0, count-1);
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:lastPos inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
    }
}

On viewDidLayoutSubviews and it finds the correct place on the bottom (or a very close estimated position).

I hope that helps.

Solution 5 - Ios

For my case, I found a temporary workaround by not suggesting an estimated cell height to the program. I did this by commenting out the following method in my code:

- (CGFloat) tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

However, please take note that doing so may affect the user experience when the user scrolls, if your cells varies a lot compared to each other. For my case, no noticeable difference so far.

Hope it helps!

Solution 6 - Ios

I have this problem in Swift 5 iOS 13 yet, this solved my problem

 DispatchQueue.main.async { [weak self] in
    self?.tableView.reloadData()
    self?.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
 }

Solution 7 - Ios

My solution was to use the size of the storyboard as the estimate.

So instead of this:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {   
return UITableViewAutomaticDimension;

}

I did something like this:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { 

MyMessageType messageType = [self messageTypeForRowAtIndexPath:indexPath];

switch (messageType) {

    case MyMessageTypeText:
        return 45;
        break;

    case MyMessageTypeMaybeWithSomeMediaOrSomethingBiggerThanJustText:
        return 96;
        break;
        
    default:
        break;
 }
}

I'm writing a chat table view so it is likely that many of my cells, specifically that text type will be larger than what is in IB, especially if the chat message is very long. This seems to be a pretty good...well...estimate and scrolling to the bottom gets pretty close. It seems to be slightly worse as the scrolling gets longer, but that is to be expected I suppose

Solution 8 - Ios

Just call tableview reloadData after viewDidAppear can solve the problem

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self.tableView reloadData];
}

Solution 9 - Ios

Although as smileyborg's answer it is bug in iOS 8.x, it should be fixed in all platforms which you supports...

To workaround on pre-iOS9, below code do the trick without any dispatch_async or dispatch_after. Tested on iOS 8.4 simulator.

UPDATE: Calling (only) layoutIfNeeded does not work when view controller become visible by UIPageViewController being scrolled. So use layoutSubviews (or maybe setNeedsLayout + layoutIfNeeded) instead.

// For iOS 8 bug workaround.
// See https://stackoverflow.com/a/33515872/1474113
- (void)scrollToBottomForPreiOS9
{
    CGFloat originalY, scrolledY;
    do {
        // Lay out visible cells immediately for current contentOffset.
        // NOTE: layoutIfNeeded does not work when hosting UIPageViewController is dragged.
        [self.tableView layoutSubviews];
        originalY = self.tableView.contentOffset.y;
        [self scrollToBottom];  // Call -scrollToRowAtIndexPath as usual.
        scrolledY = self.tableView.contentOffset.y;
    } while (scrolledY > originalY);
}

Solution 10 - Ios

I had the same problem when creating a chat tableView with different height of cells. I call the code below in viewDidAppear() lifecycle method:

// First figure out how many sections there are
let lastSectionIndex = self.tableView.numberOfSections - 1
    
// Then grab the number of rows in the last section
let lastRowIndex = self.tableView.numberOfRowsInSection(lastSectionIndex) - 1
    
// Now just construct the index path
let pathToLastRow = NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex)
    
// Make the last row visible
self.tableView.scrollToRowAtIndexPath(pathToLastRow, atScrollPosition: UITableViewScrollPosition.None, animated: true)

Please let me know if that worked for you too.

Solution 11 - Ios

From the storyboard window click in a blank area to deselect all views then click the view that has the table view in it and then click the Resolve Auto Layout Issue icon and select Reset to Suggested Constraints

enter image description here enter image description here

Solution 12 - Ios

Use this simple code to scroll bottom

 var rows:NSInteger=self.tableName.numberOfRowsInSection(0)
        if(rows > 0)
        {
            let indexPath = NSIndexPath(forRow: rows-1, inSection: 0)
            tableName.scrollToRowAtIndexPath(indexPath , atScrollPosition:  UITableViewScrollPosition.Bottom, animated: true)
        }
            }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBenBView Question on Stackoverflow
Solution 1 - IossmileyborgView Answer on Stackoverflow
Solution 2 - IosabinopView Answer on Stackoverflow
Solution 3 - IosBhavesh TiwariView Answer on Stackoverflow
Solution 4 - IosGui MouraView Answer on Stackoverflow
Solution 5 - IosMarcusView Answer on Stackoverflow
Solution 6 - IosHamedView Answer on Stackoverflow
Solution 7 - IosRoderic CampbellView Answer on Stackoverflow
Solution 8 - IosEvan JIANGView Answer on Stackoverflow
Solution 9 - IosyprestoView Answer on Stackoverflow
Solution 10 - IosStefan OlaruView Answer on Stackoverflow
Solution 11 - IoskendotwillView Answer on Stackoverflow
Solution 12 - IosBibin JosephView Answer on Stackoverflow