はじめに
UITableView
で reloadData()
をしたときにトップへスクロールしたいときがたまにあります。でもこれが結構めんどくさい。。。
色々方法を試したので備忘録として記載します。
やりたいことは「ボタン押下したときにリロード+スクロール位置をトップに戻したい」ということ!
こんな感じ(before が最下部表示で after が before の状態からリロード+トップへスクロールした状態です)
before | after |
---|---|
テーブルは前回の記事で書いたヘッダーが消えたり出たりする grouped
スタイルのテーブルでやります!
(before はヘッダー非表示、after はヘッダー表示です)
方法
テーブルをトップにスクロールする方法として setContetnOffset
と scrollToRow
があり、 reloadData
の前にスクロールさせるのか後にスクロールさせるのかで4パターンできると思います。
実行結果1
実装はこんな感じ
// パターン1(setContetnOffset先スクロール)
tableView.setContentOffset(.zero, animated: false)
tableView.reloadData()
// パターン2(scrollToRow先スクロール)
tableView.scrollToRow(at: .init(row: 0, section: 0), at: .top, animated: false)
tableView.reloadData()
// パターン3(setContetnOffset後スクロール)
tableView.reloadData()
tableView.setContentOffset(.zero, animated: false)
// パターン4(scrollToRow後スクロール)
tableView.reloadData()
tableView.scrollToRow(at: .init(row: 0, section: 0), at: .top, animated: false)
結果はこんな感じ
パターン1 | パターン2 | パターン3 | パターン4 |
---|---|---|---|
パターン1とパターン4(でも row 設定なのでヘッダーまではスクロールしてない)はいけてそうだけどパターン2、3が中途半端な位置になっている。。。
実行結果2
なんかわからんけどとりあえずコンテンツサイズが確定してない=レイアウトが中途半端な状態だから上のような結果になるんだろうと思い layoutIfNeeded()
呼べばいいんじゃね?ということで下記のように実装してみた。
// パターン1(setContetnOffset先スクロール)
tableView.setContentOffset(.zero, animated: false)
tableView.layoutIfNeeded()
tableView.reloadData()
// パターン2(scrollToRow先スクロール)
tableView.scrollToRow(at: .init(row: 0, section: 0), at: .top, animated: false)
tableView.layoutIfNeeded()
tableView.reloadData()
// パターン3(setContetnOffset後スクロール)
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.setContentOffset(.zero, animated: false)
// パターン4(scrollToRow後スクロール)
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.scrollToRow(at: .init(row: 0, section: 0), at: .top, animated: false)
結果はこんな感じ
パターン1 | パターン2 | パターン3 | パターン4 |
---|---|---|---|
全パターンいい感じにいけてそう
実行結果3
layoutIfNeeded()
を呼ぶことでそれぞれ思った通りの動作になってますがあまいです!セルの高さが固定なら問題ないのですがセルの高さが可変の場合はちょっと。。。
下記のような2パターンのセルを用意します。
実行結果2の処理を試してみると結果はこんな感じ(ちなみに layoutIfNeeded
ないパターンだとパターン4以外は中途半端なスクロールになりました)
パターン1 | パターン2 | パターン3 | パターン4 |
---|---|---|---|
パターン3以外はいけてそうですおそらく tableView(_ :estimatedHeightForRowAt:)
で適切な値を返してやるとパターン3でもうまくいくと思います。2パターンとかならいいですがここにオートレイアウトとか関与してくると色々高さ計算がめんどくさくなります。。。
セルの高さに関しては下記の記事に色々丁寧に記載されていたので参考に(私はあんまりわかってない。。。)
UITableViewのrowHeightやestimatedRowHeightに何を設定すると良いのか
結論
今回は計算とかいろいろめんどくさいのとヘッダーまでスクロールしたかったので下記を採用しました。(たぶんいけてそう)
// パターン1(setContetnOffset先スクロール)
tableView.setContentOffset(.zero, animated: false)
tableView.layoutIfNeeded()
tableView.reloadData()
動作はこんな感じ
20201024追記
コメントでいただきましたが下記のようにスクロール位置を .top
から .bottom
に変更するといけそうです
tableView.reloadData()
tableView.scrollToRow(at: .init(row: 0, section: 0), at: .bottom, animated: false)
layoutIfNeeded
も必要ないですしこれが一番いいんじゃないかなと思います
おわりに
reloadData
とトップへスクロールの組み合わせはわりとやることあるんですが結局どういう方法がいいのかな?といつも悩んでしまいます。。。
下記のようなやり方もあるみたいです。
iOS TableView reload and scroll top
他なにかいい方法ご存知であればぜひご教授ください