Edited at

[iOS]UIStackView+ContainerView+UIScrollViewで複雑な画面を設計する

今更なネタですが。

アプリのコンテンツ量が多くなってくると、いろんなコンテンツが混在した画面というものが登場します。最たる例はトップ画面です。

こんな雰囲気。

Untitled Diagram (31).png

単純な場合はどのように組んでも問題は生じないのですが、次の場合頭を抱えることになります。


  • 横スクロールが伴う

  • 一部のコンテンツが、状況によって出たり出なかったりする

  • 出たり出なかったりするのがAPIのレスポンス依存(後から決まる)

  • APIが別れている、複数ある

  • 高さがコンテンツの量に応じて変わる

  • 高さがアニメーションにより増減する

  • タッチイベントが競合する

  • Viewが重い

  • 一番下だけ無限スクロールでページングする

  • スイッチが有り、コンテンツが切り替わる

こういった画面に遭遇した時、UITableView、UICollectionView、UIStackViewのどれかを使うことを検討すると思います。

そのUIStackViewでどう組むかという話です。


対象

UIStackViewがある程度分かる人


UITableVIewとUICollectionViewではダメなのか?

ダメではないと思います。

しかしこれらはハマりポイントが多数存在しがちです。


  • 高さの変更でハマる(特にアニメーション)

  • 非同期でAPI取得後、表示・非表示を制御する時にハマる

  • Cellの場合は表示直前に生成するため、Cell内のViewが多いとスクロールする際にカクつくことがある(例えばCellの中にUICollectionViewが含まれている場合など)

  • Cell内にUICollectionViewがあったりすると、設計に悩むしライフサイクルでも悩む

それでせっかく作ってもあーだこーだ調整に時間が掛かってしまうというのが過去数件ありました。

UIStackViewがこの全てを解決するわけではありませんが、仕様によってはUIStackViewを使うことでスッキリ書けることがあります。


UIStackViewが向かないもの

同じようなViewが連続する場合はもちろんCellを使ったほうがよいでしょう。

また、コンテンツ量が非常に多い場合なども向きません(といってもこれは本当に多いケースです)

ヘビーな画面を作る際は、画面表示時に1回だけ重いか、Cell表示時に毎回重いかどちらかを選択することになると思います。


ContainerViewでどのように高さを変更するか問題

これが本題です。

UIStackViewの中にContainerViewを含める場合、高さの制御が問題になります。

なぜか内部のUIViewContollerで自身のViewの高さをAutoLayoutで指定してもUIStackViewの高さに反映されてくれないのです。

できればUITableViewAutomaticDimensionのように、内部側から高さを決めたいですよね。

それを実現するためには、内部のUIViewControllerでこうします。

override func loadView() {

super.loadView()
self.view.translatesAutoresizingMaskIntoConstraints = false
}

これで解決します(理由は察せると思うので割愛。何だそんなことかー、と思いました)


サンプル

https://github.com/osanaikoutarou/EasyStackViewControllers

こういう状態で、ContainerViewをUIStackViewに入れます。

スクリーンショット 2019-06-27 1.50.21.png

画面A

UITextViewのScrollableはオフにしています

スクリーンショット 2019-06-27 1.52.13.png

画面B

UIImageViewの高さを固定しています

スクリーンショット 2019-06-27 1.52.25.png

画面C

Addを押すとViewが追加されていきます

スクリーンショット 2019-06-27 1.53.11.png

実行結果

ezgif-2-fa9c8bcdf1c0.gif

このように、内部の高さに応じてUIStackView側の高さも変わっています。

一番上のViewControllerの中身はこの状態です↓

空っぽ!

スクリーンショット 2019-06-27 2.03.07.png


鬼門:UIScrollView

何度やってもハマるUIScrollViewのAutoLayout

参考にどうぞ

スクリーンショット 2019-06-27 2.06.45.png

UIScrollView -> 画面view 上下左右

ContentView -> UIScrollView 上下左右+EqualWidth

StackView -> ContentView 上下左右

このままだとエラーが出るので

UIView -> StackView 高さ固定

高さ固定を使わないなら、Remove at build timeにチェック

スクリーンショット 2019-06-27 2.09.26.png

このViewを使わないなら、viewDidLoadあたりでStackViewの中身を空に

(ここらへんもっとスマートな方法無いんですかね?AutoLayoutのエラーを放置する手もありますが)


UIStackView in UIStackViewは可能か?

上のサンプルの画面Cで既に使っています

大丈夫です


高さアニメーションしたら、見た目が少し変

こちら

[iOS]UIStackViewのアニメーションが変!


Viewにするか、ViewControllerにするか迷う

こちら

https://stackoverflow.com/questions/2632196/what-is-the-difference-between-a-view-and-a-view-controller


別解

ライブラリーを使うてもあるかも知れません(学習コストが高そうで、まだ使ったことありません)

StackViewController

https://github.com/seedco/StackViewController

AloeStackView

https://github.com/airbnb/AloeStackView


積み残した話題

一番下のコンテンツが無限スクロール(ページング)の場合はどうする?🤔

CellとStackViewのコラボレーションは少しハマりどころがあるのでまた別の機会に

あと、パーツをViewControllerにするにはちょっとコスト高いという場合にどう定義するのがスッキリするか、少し悩んでます

(UIStoryboard内にCustomViewを定義して、IBOutletを繋げない問題。Cellなら可能なんですけどねえ)


追記

このトピック無いなと思って書いたんですが、ありました。

巨大なビューをStoryboardだけで表現する

ContainerView周りの話題は認識しておいたほうが良いですね。