Auto Layout で UIScrollView を使うときによくわからなかったこと

今日よくわかるようになったので書きます。

Scroll View の Auto Layout edge は意味が違う

Storyboard から Scroll View を使うとき、こんな風に Scroll View に Content View を入れて、その Content View の上下左右の Constraint を Scroll View の上下左右にぴったりくっつけるのが Apple もそうしろと言っているやり方です。

意味わからなくないですか?

これじゃあ「 Scroll View の高さと Content View の高さが一緒になっちゃうんじゃないの?」って思いませんか?きっとどんな iOS 開発者も、きっとはしかのようにみんながかかっていく疑問だと思うんです。4月に初めたばかりの僕も、ずっとこれが引っかかっていたんですが、

この渋いドキュメントにしれっと

In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to the left edge of its superview, you’re really pinning it to the minimum x-value of the superview’s bounds. Changing the bounds origin of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view.
The constraints on the subviews of the scroll view must result in a size to fill, which is then interpreted as the content size of the scroll view. (This should not be confused with the intrinsicContentSize method used for Auto Layout.) To size the scroll view’s frame with Auto Layout, constraints must either be explicit regarding the width and height of the scroll view, or the edges of the scroll view must be tied to views outside of its subtree.

と書いてあるのを見つけました。

Scroll View の Auto Layout の top / left / bottom / right は他の View のそれらとは意味が違う。
Scroll View の中身の四辺の端となる辺をプログラマが表明するためのフックポイントだよ。

ってこんなところにしれっと書いて終わりにするなよ!すごい困ったよ!

「スクロールビューさん、あなたの中にいる要素の、この辺を中身の上辺だと思ってください。で、この要素のこちらの辺が中身の下辺だと思ってください。

中身の高さはときどき変わるかもしれませんが、このお伝えした辺まできたら、それは常に中身の端ですので」

というわけなんですね。だからやろうと思えば複数要素でもやれるんだ。

単体要素でやる場合、通常なら top と bottom の Constraint を付けたら height が決まるはず。でも Scroll View の場合は それを Content View に教えてくれないので、どこかしらに自分でちゃんと付けてあげないといけないんですね( Scroll View 自体には無理だけど、 Scroll View を飛び越して Scroll View の親と Content View の高さを揃える、とかはできるので、色々工夫次第みたい)

現にこれが通常の View の組み合わせで、 top / bottom と矛盾する height を付けた場合の Storyboard 上の表示です。

矛盾しているので怒っています。

こちらが Scroll View の中に入れた Content View が同じことをした場合の Storyboard 上の表示です。

怒ってないです。

一度わかると、こちらのようにさらっと書いてあるところをいくつか見かけるようになりました。でも逆は無理です。たぶん何言ってるかわからなかったと思います。

Content View の height をプログラムで入れたくない

いまは可能な限り Storyboard と Auto Layout でやってみようとしています。

この場合 Content View の height をどうしたらいいのか考えていたのですが、 Content View の中身をすべて上から積んでいって(制約していって)、最後に一番下に来る要素の bottom と Content View の bottom を制約するようにするのですね。

上から下へスクロールされる Content View の中身が下からレイアウトされることはあり得ないので、これで良さそうでした。

Label から Content View へ Ctrl+ドラッグして Bottom Space to Container するのと、 Content View と Label を両方選択して

Alignment Constraints ダイアログで Bottom Edges を揃えるのは結局同じ Constraint が生成されるんだと思いますが、 Alignment Constraints ダイアログほとんど使ったことがなくて今日発見してここでしかできないみたいな感じで同僚に激しく自慢してしまったことを今深く恥じます。

Constraint はやり方が複数あって怖いなあ。

Apple のドキュメントですらうすらぼんやりだし、 Scroll View は理由の書いてないノウハウがインターネット上にとにかくいっぱいあって恐ろしかったです。でも、もうわかったので良しとしたいと思います。