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


 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を調整するやり方でできるかも。


```objectivec

// 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](https://qiita-image-store.s3.amazonaws.com/0/1209/52930ded-ad58-d688-3936-f003e2185818.png "sampleImage.png")