56
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UITableViewでestimatedRowHeight使ってreloadDataすると位置がずれる件の対処

Last updated at Posted at 2014-05-26

UITableViewにestimatedRowHeightを指定してreloadDataを呼ぶと、セルのスクロール位置がおかしな位置にジャンプしてしまいます。これをジャンプしないようにします。

何をやっているか:

UITableViewでreloadDataを呼ぶと、現在表示中のセル(visibleCells)と その1つ上のセルに対して tableView:heightForRowAtIndexPath:が呼ばれるようです(iOS7.1.1で確認)。それ以外のセルの高さは、たとえ以前に正確な高さを取得していてもestimatedRowHeightを使うようリセットされ、それによってreloadData前と後で表示中のセルのy座標が変化すると思われます(推測)。

そこで、表示中のセルのy座標がreloadData前後で同じになるようにestimatedRowHeightの値を計算して変更します。強引ですが、結果的にスクロール位置がジャンプしなくなります。

注意事項:

  • 現時点のiOS(7.1.1)の動きを調査して推測によって処理を書いているので、iOSのバージョンがあがると正しく動作しなくなる可能性があります。
  • この方法でも、まれに少しジャンプすることがあります。原因不明。
  • estimatedRowHeightと似たメソッドであるtableView:estimatedHeightForRowAtIndexPath:には非対応です。estimatedHeightForRowAtIndexPath:の呼ばれ方が謎すぎる。

その他

スクロール位置を指定する他のメソッドもreloadDataしてからestimatedRowHeightを調整するやり方でできるかも。


// fix : UITableView using estimatedRowHeight jump the scroll position at reloadData.
-(void)reloadTable {
    // このメソッドは、UITableViewControllerに、高さの異なる複数のセルが表示されている前提です。
    // UITableViewでもたぶん動作します。
    
    if (!self.tableView.estimatedRowHeight ||
        ([[self.tableView indexPathsForVisibleRows] count] <=1))
    {
        [self.tableView reloadData];
        return;
    }
    
    // visibleRowsの1つ前のcellからheightForRowAtIndexPathが呼ばれるので、1つ前のindexPathを取得。
    NSIndexPath *path0 = [self.tableView indexPathsForVisibleRows][0];
    if (path0.section == 0 && path0.row == 0) {
        [self.tableView reloadData];
        return;
    }
    NSIndexPath *calculateStartPath = (path0.row ==0) ? [NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:path0.section-1]-1 inSection:path0.section] : [NSIndexPath indexPathForRow:path0.row-1 inSection:path0.section];
    
    // estimateで数えられる(画面外上方向にある)セルの数を数える
    long cellCount = calculateStartPath.row;
    for (int section=0; section < calculateStartPath.section; section++) {
        cellCount += [self.tableView numberOfRowsInSection:section];
    }

    if (cellCount <= 0) { //estimateされるセルが無いなら位置調整不要。reloadして終了
        [self.tableView reloadData];
        return;
    }
    
    // セクションヘッダ、セクションフッダのサイズを計算。
    // estimatedSectionHeaderHeight,estimatedSectionFooterHeightは使われないっぽいので、heighForHeaderInSection:等を使う。
    CGFloat headerHeights = 0.0f;
    CGFloat footerHeights = 0.0f;
    for (int section=0;section <= calculateStartPath.section;section++) {
        headerHeights += [self.tableView.delegate tableView:self.tableView heightForHeaderInSection:section];
    }
    for (int section=0;section < calculateStartPath.section;section++) {
        footerHeights += [self.tableView.delegate tableView:self.tableView heightForFooterInSection:section];
    }

    // estimatedRowHeightを、現在のセルのy位置に合うように調整する
    CGRect rect = [self.tableView rectForRowAtIndexPath:calculateStartPath];
    self.tableView.estimatedRowHeight = (rect.origin.y - headerHeights - footerHeights - self.tableView.tableHeaderView.bounds.size.height ) / cellCount ;
    
    [self.tableView reloadData];
}

サンプル

サンプルをgithubに置きました。
https://github.com/hirobe/EstimatedTable
Reload(default)ボタンを押すと位置がずれますが、Reload(tuned)ボタンを押すと位置がずれません。中では両方ともreloadDataを呼んでいます。
sampleImage.png

56
61
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?