state restoration を実装していないアプリなんて存在しないですよね?
ちなみに Xamarin 環境でのメソッド名にて記述するので,Obj-C や Swift では読み替えてください。
UITableView の state restoration は難しい。
特に可変の高さを使っている場合など,ちょっと復元方法にテクニックがいるっぽいですね。
まず,状態保存に関して。
状態保存 (EncodeRestorableState)
座標TableView.VisibleCells
contentOffset
のセル (下記) から ViewModel の Id を取得。その値 (string) を保存。*1TableView.IndexPathForSelectedRow
から ViewModel の Id を取得し,同様に保存。
座標のセルを取るための補助メソッドは以下の通り。
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static UITableViewCell GetCellAtPoint(this UITableView tableView, CGPoint location) { var indexPathAt = tableView.IndexPathForRowAtPoint(location); if (indexPathAt == null) return null; return tableView.CellAt(indexPathAt); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T GetCellAtPoint<T>(this UITableView tableView, CGPoint location) where T : UITableViewCell => tableView.GetCellAtPoint(location) as T;
var cell = TableView.GetCellAtPoint<YourTableViewCell>(TableView.ContentOffset);
でよい。ただし,NavigationBar や TableViewHeader を考慮して取得しないとターゲットセルがかなりずれるので,実際には ContentOffset 直指定は不可。
状態復元 (DecodeRestorableState)
Realm から読み出す場合,実際のデータベースからの読み込みに多少の時間がかかるため,非同期処理でロードスピナーを表示し,そして復元できる状態になって適切に復元するのが好ましい。
- ViewModel として Realm からデータが適切に読み込まれているか,確認。読み込まれていれば復元処理に飛ぶ。
- 100% の確率で復元に成功していないので,Rx を使って,ロード処理が終わるまで待機。
- ロード処理が終わった場合,データが空でないか確認。空の場合,データベースが消失等が考えられ,この状態では復元できないので,復元しないのが適切。
- 以上の条件が全て通過している場合,復元可能。
ちなみに,復元する前に,TableView.ReloadData()
を呼び出さないと,ScrollToRow
を使って正しい位置にスクロールされないため,必ず復元前に入れること。
iOS 7 となっていますが,iOS 11.4 現在でも必要です。
まとめ
具体的なコードは書いていませんが,プロダクトによってかなり復元の仕方が異なると思うので,書いていません。
面倒臭いですが,state restoration はマルチタスクを利用する際に重要です。ViewModel から Cell に復元する処理は色々あると思いますが,Section - Data の 2 レベルリストを利用しているので,Data.Id
を使って実態を検索し,IndexOf を使って NSIndexPath を求めるようにしています。
もっと復元にいい方法とかあるんですかね。めっちゃ泥臭いコーディングになってるのが個人的に気に食わないですが,とりあえず暫定的にこういうコード書いています……w
*1:スクロール位置で保存してしまうと,バックグラウンドで追加取得した場合など,実際のデータ表示位置とスクロールした位置がずれるためこの方法の方が好ましい