【Swift】複数のコンテンツをUIScrollViewでAuto Layoutで並べる方法

スクロールする画面の中に綺麗にコンテンツを並べるには?
ちゃんと設定してるはずなのに、UIScroll Viewの中に入れると消えるんだけど?
こんな疑問に答えます。
App Storeで2017年からiPhoneアプリを公開中の僕が解説していきます。

普通のオブジェクトをAuto Layoutで並べるだけでも大変なのに、UIScrollViewが絡んでくるとやっかいですね。
かなり複雑なことになってます。

UIScrollViewは概念的なものと認識した方がよさそうです。
UIScrollViewを通り越して、ViewとContents、つまり親と孫の関係性(外側と内側の中身のコンテンツとの間)でサイズをしっかりと定義することが大事です。

では、解説していきます。

作業環境

Xcode Version 10.0 (10A255)
Mac OSX High Sierra
Swift 4.2

お手本サイトのやりかたのコツ

やり方ですが、こちらのサイトに載っています。

iOS: How To Make AutoLayout Work On A ScrollView
https://www.natashatherobot.com/ios-autolayout-scrollview/

GitHubにコードも公開されています。

NatashaTheRobot / ScrollViewAutolayoutExample(GitHub)
https://github.com/NatashaTheRobot/ScrollViewAutolayoutExample

基本的に、こちらのサイト様を参照すればいいと思います。
気づいたことを、日本語で補足させていただきます。

お手本コードのAuto Layoutをチェックする

お手本のコードを開いて、Auto Layoutの部分の制約一覧を見ると、わかりやすいです。
制約の数多いです…

エラーが出る人はSwiftバージョン指定をチェック
(:-1: Value for SWIFT_VERSION cannot be empty. (in target 'ScrollViewAutoLayoutExample'))というエラーメッセージが出るかもしれません。
お手本のコードで、このエラーが出たら、左サイドメニューからプロジェクトを選択し、TARGETS>Swift Langage version が空欄になっているところを、4.2とか選びます。

ScrollViewでAuto layoutで並べるのはApple開発者でも難しいらしい

This “simple” solution took the Apple Engineer 40 minutes to solve! However, several senior engineers I know said that they’ve never been able to get AutoLayout working quite right on a ScrollView, so 40 minutes is actually not bad!
https://www.natashatherobot.com/ios-autolayout-scrollview/

このように、WWDCでApple開発者と会話した時のエピソードが書いてありますね
WWDCにいた開発者は40分で解決したみたいですが、全員が解けるお題ではないようですね。

やっぱりAuto layoutの難しさはみんなが感じていることなんですね…
よかった…
安心しました
俺もずっと不思議に思ってました…この難題パズル…みんな普通に解いてんの?って

View(親)、Scroll View(子供)、Content View(孫)の3世代の関係性が重要

View(親)、Scroll View(子供)、Content View(孫)の3世代を入れ子にします。
ここで、親と孫の世代をまたいだ制約が必要です。
そこが最大のポイントだと思いました。

1つのScroll View(子供)の下には1つのContent View(孫)しか置かない

Scroll View(子供)はContentView(孫)のによって自動で姿を変える概念的存在ってことですかね。
複数のコンテンツがあるときは、ContentView(孫)の中に、部品をいくつも並べればいい。
Scroll View(子供)は単純にContentView(孫)を入れるだけでいいみたいです。

でも…これに関しては、複数置いてもいいと思うんですけどね。
下の方に書きました。

ContentView(孫)とView(親)との間に「Equal Width」 の制約を加える

ここが最重要ポイントですね
(ってか、こんなの普通にやってたら…思いつかないだろ)

ContentView(孫)とView(親)との間に「Equal Width」 の制約を加えます。

ContentViewとViewとの間の制約を作るには、階層表示のウィンドウでわざわざドラックする操作が必要です。
論理的に理解できてないと、この辺、無理ですね。
かなりの難易度です。
知らないと無理でしょ、これ。

理由は、Scroll View(子供)はContentView(孫)の幅によって調整されるから、とのことですね。
先ほど同様、Scroll View(子供)はサイズという概念が存在しないってことですね。

応用編
応用技として、次のようなのもできますね。
・「Equal Width」「Equal Heights」の両方を指定する
・高さを数値で指定する
サイズが明確に定義されればいいので、これでも大丈夫なようです。

ScrollViewとContentViewの間の制約を設定する

先ほどから話しているように、Scroll View(子供)は単純にContentView(孫)を入れるだけなので、ぴったり隙間なく埋めます。
上下左右隙間0です。

ContentViewに制約を追加して、全体のレイアウトをする

このサイトでは、コードの方(ViewContorller)でContentView(孫)を真ん中に配置しています。
ContentView(孫)自体を配置するときも、感覚的にはScroll View(子供)を動かしたくなりすが、ContentView(孫)を動かすということでいいと思います。

AutoLayoutをContentViewに追加する

これはContentView(孫)の中身のことですね。
まあ、ここは別に自由に何でもいいですね。
僕は文字入力フォームを4つ並べています。

関連する課題

ただ、これだけだと僕のやりたいことにはまだ課題があるので、書かせてもらいます。

ContentView(孫)を複数並べる方法

今回の記事で発覚したように、UI ScrollViewの直下のContentView(孫)は縦と横のサイズをしっかり定義しておくことが大事です。

しかも、ContentView(孫)のなかにViewを入れ込むことも多いので、実質こんな感じになりますね。

View(親)
>>Scroll View(子供)
>>>>OrangeContentView(孫)
>>>>BananaContentView(孫)

Orage、Bananaを並べたい場合も、両方でサイズをしっかり定義するのが大事ですね。

UI Scroll Viewを文字入力の時に自動スライドさせる

せっかくスクロールできるんだから、文字入力の時にグルグルっと自動スクロールしてほしんです。
別記事で書きます。
その準備段階として、今回の作業が必要でしたね。

というわけで今回は以上です。

お手本のコードを参照させていただきました。
View(親)、Scroll View(子供)、Content View(孫)の3世代の関係性を把握する。
ContentView(孫)とView(親)との間にサイズ の制約を加えるってところが最重要でしたね。

Auto Layoutはやはり、みんな手こずっているようなので、逆に出来ると自信が湧いてきました。
最近、iPhoneの種類もサイズも増えてきているので、引き続きAuto Layoutを極めていきたいです。

-Swift