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を呼んでいます。