Xcode11の新機能でUIScrollViewのAutoLayoutを攻略

2020-07-12

久しぶりにUIScrollViewダンジョンに入ったら攻略できなかったので攻略法の備忘録。

背景

たくさんの人が利用しているだろうSnapKitをうちでも使用していて、ScrollViewにAutoLayoutかけようと思ったら難しかったです。

ポップアップのようなビューを表示していたんですが、SE端末だけ下のタブバー上のナビバーの間の表示領域を飛び出してしまうので、上限がきたらスクロールに切り替えるという実装をすることになりました。

満たしたい条件

scrolll.png

  • SnapKit を使用(以下ちょいちょいこれ使用の前提で話します)
  • ボタンやらラベルやら詰め込まれているViewを使用(スクロールさせたいContentView)
  • Viewの指定
    • width最大値指定あり(iPad様の限界値を作るため)
    • height最大値指定あり(限界まできたらマージン分で止める)
    • ContentViewのheightは内部パーツで決まる可変
    • 基本中央表示
    • height限界値に到達した場合スクロールさせる、かつContentViewのtopはScrollViewのtopに合わせる

色々条件がありますがXcode11からScrollViewの仕様が少し変わったこともあり、基本形と条件指定形をまとめやり方を定着させたい。参考になる方がいれば。

ScrollViewに制約をかけるときの基本

Xcode11からScrollViewは

  • FrameLayoutGuide
  • ContentLayoutGuide

というのを持つようです。

scrollguide.png

登場人物

  • ScrollView = ScrollView自体、ContentViewを覗くフレームに直結
  • FrameLayoutGuide = 上記ScrollViewフレームのガイド
  • ContentLayoutGuide = ContentViewを合わせるガイド
  • ContentView = スクロールさせたいView

基本的にはこの子たちに制約かけていく形になります。
ざっと、
ScrollView FrameLayoutGuideで窓👓
ContentView ContentLayoutGuideで中身🌃を窓👓にどう表示したいか
を決める感じかと思います。

ContentView.frame.heightなどが可変で設定したScrollViewの高さ未満の場合、スクロールされません。

いきなり最初にあげたミッション達成条件を満たすのは難易度が高いので、順番に下の階層からダンジョンを上がっていく。

SnapKitの使用前提です。

1階層

ミッション達成条件

⭐️ 単一方向スクロール(縦or横スクロール)
⭐️ ScrollViewは全画面

でミッションコンプリートです。簡単です。
ちなみに横スクロールしたい場合は
make.height.equalTo(scrollView.contentLayoutGuide)で高さを固定すればおkです。
両方固定するとスクロールしないので注意です。

備考

低難易度のうちにどうなっているか整理したい、うやむやの中先に進むと崩壊します。

make.edges.equalToSuperView()は窓👓を作るので、作りたい窓👓の大きさを設定します。
全画面にしたいとか、superViewの下半分にScrollを作りたいとか、ここでsuperView基準で作ります。

make.width.equalTo(scrollView.frameLayoutGuide)は、
contentView(表示させたい中身🌃)の横幅をscrollView(frameLayoutGuide)に収めたいので、
中身🌃.width = 窓👓.widthとします。
縦幅(height)は設定していないので垂れ流し = frameから外れる = 外れた分がスクロール対象領域 という認識です。

make.edges.equalTo(scrollView.contentLayoutGuide)は、個人的には立ち位置の理解が少し難しかったのですが、中身🌃をScrollViewに対してどう表示させたいかを担う部分ということで今は落ち着いています。

という部分を頭に入れた上で階層を上がっていく。

2階層

ミッション達成条件

⭐️ 単一方向スクロール(縦or横スクロール)
⭐️ ScrollViewの大きさは指定条件でレイアウト
⭐️ ScrollViewはsuperViewの下半分、左右下に指定のマージン

基準はそれぞれですが、scrollViewのtopをsuperViewの高さ/2の数値にイコールとすることでsuperViewの下半分の領域をScrollViewとして確保します。

この様に窓👓自体のレイアウト指定がある場合はScrollViewに制約をかけてレイアウトを決めます。

次から少し🤔となり始める、というか、Autolayoutの正しい知識がないと討伐が大変になる。

3階層

ミッション達成条件

⭐️ 単一方向スクロール(縦or横スクロール)
⭐️ ScrollViewの大きさは指定条件でレイアウト
⭐️ ScrollViewは左右に指定のマージン
⭐️ ContentViewの高さ表示限界以内なら中央表示(超えたらスクロール)

補足しますが、この条件が意図しているところは

  • 上下は決まったマージンをとらず、遊ばせる(画面サイズまたはsuperViewによって可変)

ということなので、superViewに収まる範囲ならスクロールさせる必要はありません。
ですが例えばsuperViewの高さが小さい時など、限界突破してcontentViewの高さがsuperViewの縦幅をぶち抜いてしまった時にはスクロールを適用して差し上げます。

scrollView

make.left.right.equalToSuperView().inset(10)
make.center.equalToSuperview()
窓👓の左右マージンを固定で設置、中央にくる様に設定。

make.height.lessThanOrEqualToSuperview()
窓👓の縦幅最大値をsuperViewにして限界突破しないようにする。

contentView

make.width.equalTo(scrollView.frameLayoutGuide)
お馴染み縦スクロール設定。

限界前は中央表示、限界きたらスクロールという設定は以下でバランスとって設定しないといけない。
ただ中央表示するだけではなく、限界にきたら中央表示ではなくcontentViewのtopは上揃えというふうに
表示条件を分けなければいけない。

make.left.right.bottom.equalTo(scrollView.contentLayoutGuide)
top以外の三方向はcontentLayoutGuideに揃える。topは条件で分けるので指定しない。

make.top.bottom.greaterThanOrEqualTo(scrollView.contentLayoutGuide)
限界にきたらtop bottomをcontentLayoutGuideに合わせる。

make.center.lessThanOrEqualToSuperview().priority(.required - 1)
中央表示はcontentViewsuperViewに到達するまでに設定。優先度は下げておく。
(この辺自信がないので指摘頂けると嬉しい)

4階層

ミッション達成条件

⭐️ 単一方向スクロール(縦or横スクロール)
⭐️ ScrollViewの大きさは指定条件でレイアウト
⭐️ ScrollViewは左右に指定のマージン
⭐️ ContentViewの高さ表示限界以内なら中央表示(超えたらスクロール)
⭐️ ScrollViewの高さ表示限界に指定のマージンをいれる

ラストダンジョンは最初にやりたかった条件達成でコンプリート。
3階層が終わればここはマージン指定するだけなので難しくない。

make.height.lessThanOrEqualToSuperview().inset(5)
scrollViewの高さ最大値はsuperviewに設定していたのでインセットをかける。

これで目標条件達成できました。

上記のように、端末サイズ指定や条件分岐を入れなくても
lessThanOrEqualTogreaterThanOrEqualTopriorityを使用することで調整できる。
という認識。

終わりに

Scrollダンジョン難しい。
だがFrameLayoutGuide ContentLayoutGuideというガイドがあるのでレイアウトを作りやすくなりました。
あとSnapKitとAutolayoutの勉強になりました。

お疲れっした!

アプリリリースしました!

持ち歌とそのキーを登録できるアプリです!
自分のキーをよく忘れる方は使ってみてください!

【アプリリリース】Karaokey リリースのお知らせ