Storyboardだけでページング可能なScroll Viewを簡単定義 〜Auto Layout活用テクニック〜

## はじめに

Auto Layoutが有効になっているStoryboardでScroll Viewを定義する際、思い通りに定義できずに困ったことはないですか？Auto Layoutの警告が消えなかったり、実行時にContent Sizeが思い通りのサイズにならなかったりした経験がある人は意外と多いかも知れません。

この記事では、**Storyboardだけでページング可能なScroll Viewを定義する方法**を提案します。また、**Auto Layoutを活用して、どのような画面サイズにも対応できること**を目指します。Storyboardだけで定義するので、**ソースコードでの実装は不要**です。

ページング可能なScroll Viewのページ数が一定で、簡易的に定義する場合などに有効です。例えば、***アプリを利用開始した直後のスワイプ可能なチュートリアル画面***などに利用すると良いかも知れません。

この方法を利用すると、以下のような動作を実現することができます。動作例は4inch画面でのキャプチャですが、定義変更なしに他の画面サイズでも同様の動作と表示を期待できます。

![scroll.gif](https://qiita-image-store.s3.amazonaws.com/0/6087/24de294b-40aa-49c5-2e1b-efcba6eab23f.gif)

動作を再現できるサンプルのプロジェクトも用意しました。[GitHub](https://github.com/hedjirog/PagingScrollViewDemo)からダウンロードしてください。

## 手順

**View ControllerにScroll Viewを配置して、実行時の画面サイズで水平方向にページング可能となる定義をするためにAuto Layoutを利用することを考えます。**

以下に、水平方向にページング可能なScroll ViewをStoryboardで定義する手順を説明します。Size Classesが有効になっているStoryboard (w:Any, h:Anyを指定) を利用します。

最終的には、実際のレイアウトイメージをStoryboardで確認しやすいように、Scroll Viewを配置する画面のサイズを実行時のContent Size (2ページ分のサイズ) に変更します。

最終的なStoryboardは以下のようになります。

![storyboard0.png](https://qiita-image-store.s3.amazonaws.com/0/6087/a3fe98e4-f272-f4a8-53d1-aaf1d7fa008e.png)

### 1. 各ページのサイズの「基準となるView」を配置する**（重要）**

この記事で提案する方法の最も重要な手順です。

**実行時には画面サイズと一致する一方で、Storyboardでのレイアウト時に各ページのサイズの基準として参照可能な、「基準となるView」**を配置します。ここでは、「基準となるView」として、Container Viewを利用します。

#### 1-a. 「基準となるView」に対して実行時に画面の幅と一致するように制約を加える

まずは、画面サイズを満たすようにContainer Viewを配置して、実行時に画面の幅と一致するように制約 (Constraint) を加えます。また、幅の制約だけでは不十分なので、View ControllerのViewの左、上、下の余白が`0`になるような制約も併せて加えます。

この際、Container Viewを配置するとChild View Controllerが自動生成されるので、生成されるChild View Controllerは削除しておきます。

「基準となるView」となるContainer Viewを配置して、画面の幅と一致する制約などを加えると以下のようになります。

![storyboard1a.png](https://qiita-image-store.s3.amazonaws.com/0/6087/71fe5523-590f-fe5b-3b22-e7db22b6b904.png)

##### **補足：Container Viewを利用する理由**

Container Viewを利用したのは、[Auto Layout Guide](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html)の`Auto Layout by Example`でScroll View内の要素を特定位置に固定するためにContainer Viewが利用されていたためです。Container Viewを利用すると、StoryboardでViewを重ねた際に不意にSubviewになってしまうのを防いだり、特別なViewであるということの意図が伝わるのを期待できたりするというメリットがあるのかも知れません。（正確な理由はわかりませんでした、、）

#### 1-b. 「基準となるView」に対してレイアウト時に各ページのサイズの基準となるように制約を加える

後の手順でScroll Viewを配置する画面の幅を変更しますが、画面の幅を変更しても「基準となるView」の幅が変動することがないようにします。これは、Storyboardでのレイアウト時に各ページのサイズの基準として参照可能にするための処置です。

「基準となるView」に対して、新たに幅の制約を加えます。この制約は、他の要素との相対的な指定ではなく、絶対値を指定します。画面の幅が`600pt`なので、この例では`600pt`の固定値で幅の制約を加えます。

「基準となるView」に、固定値の幅の制約を加えると以下のようになります。

![storyboard1b0.png](https://qiita-image-store.s3.amazonaws.com/0/6087/c33764bd-7e89-473f-1dcb-4140ea6b03a7.png)

ただし、この制約は、**レイアウト時にのみ必要なのであって、実行時には不要となる制約**です。このような制約を指定したい場合に利用できるのが、制約の`Placeholder`オプションです。Storyboardで制約を選択して、`Placeholder` (`Remove at build time`との説明がある項目) のオプションを有効にします。

![storyboard1b1.png](https://qiita-image-store.s3.amazonaws.com/0/6087/c4d8e408-3e69-0b87-357b-ac23527af887.png)

この時点で、「基準となるView」には2つの幅の制約が存在します。

* 実行時の画面の幅と一致する制約
* 固定値の幅の制約 (`Placeholder`オプション有効)

このままでは、**Storyboardで画面の幅を変更した際、矛盾が生じて警告が表示されてしまいます。**そのため、2つの幅の制約について優先度を調整しておきます。具体的にはStoryboardで各制約の`Priority`設定を変更します。

実行時の画面幅と一致する制約の優先度が低く、固定値の幅の制約の優先度が高くなるようにします。Storyboardでのレイアトのしやすさを優先して、レイアウト時に利用する制約をより優先するためです。

ここでは、実行時の画面幅と一致する制約の`Priority`の値を`High (750)` に、固定値の幅の制約の`Priority`の値を`Required (1000)` にします。（2つの制約の優先度付けができれば他の値を指定しても構いません。）

![storyboard1b2.png](https://qiita-image-store.s3.amazonaws.com/0/6087/741d8dd9-67fb-aeae-dea3-85fa38ec1239.png)

### 2. Scroll Viewを配置する画面サイズを変更する

ここまで正しく設定できていれば、画面サイズを変更してもStoryboardで「基準となるView」の幅が変動することはありません。

View Controllerの`Simulated Size`の値を`Freeform`に変更して、画面の幅を2ページ分の`1200pt`に変更します。

![storyboard2.png](https://qiita-image-store.s3.amazonaws.com/0/6087/18b91fec-437b-593f-c1fa-fb15a745503f.png)

### 3. Scroll Viewを配置する

画面サイズを変更できたらScroll Viewを配置します。
実行時にScroll Viewは画面サイズと一致して欲しいので、画面サイズと一致するように配置をしてから制約を加えておきます。

![storyboard30.png](https://qiita-image-store.s3.amazonaws.com/0/6087/a043eaba-103f-7d71-ef2a-ad20c7a4312d.png)

また、Scroll Viewがページング可能になるよう`Paging Enabled`のオプションを有効にしておきましょう。

![storyboard31.png](https://qiita-image-store.s3.amazonaws.com/0/6087/bb67c2a1-bed7-cfb7-4d58-5a6cdbf8538a.png)

### 4. Scroll View内に各ページを配置する

配置したScroll Viewにページとなる2つのViewを配置します。

配置した各ページのViewは、「基準となるView」との間に幅と高さが一致するような制約を加えます。
Controlキーを押しながら、各ページのViewから「基準となるView」へドラッグ操作をすることで、簡単に制約を加えることができます。
`Equal Widths`と`Equal Heights`を選択して制約を加えましょう。

![constraints2.gif](https://qiita-image-store.s3.amazonaws.com/0/6087/d210a165-25c6-3734-f916-9df9f1803184.gif)

最終的に1ページ目のViewには以下の制約を加えることになります。

* 「基準となるView」と幅と高さが一致する制約
* Scroll Viewとの左、上、下の余白が`0`となる制約
* 2ページ目のViewとの水平方向の間隔が`0`となる制約

![storyboard4.png](https://qiita-image-store.s3.amazonaws.com/0/6087/c933c861-fd0c-0788-2cf3-9ffc90ddbe84.png)

2ページ目のViewについても、1ページ目の制約を参考に適当な制約を加えてください。

冒頭でも示しましたが、完成したStoryboardは以下のようになります。

![storyboard0.png](https://qiita-image-store.s3.amazonaws.com/0/6087/a3fe98e4-f272-f4a8-53d1-aaf1d7fa008e.png)

## 最後に

この記事では、**Storyboardだけでページング可能なScroll Viewを定義する方法**を紹介しました。Container Viewを利用して、実行時の画面サイズとの制約を定義しながらも、レイアウト時に各ページのサイズの基準として参照されるView（「基準となるView」）の定義を可能としました。

ページングする各ページを細かく作り込んだり、動的にページ数が変わったりする場合などは、各ページを独立して定義して`UIPageViewController`を利用して各ページの遷移をソースコードで実装することを考えても良いかも知れません。

今回、提案した方法は、ページング可能なScroll ViewをStoryboardで定義するひとつの方法に過ぎないと考えています。他の方法などありましたら、この記事のコメント欄などにぜひ書き込んでください。

## 参考

* [Auto Layout Guide - Auto Layout by Example](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html)
* [Auto Layout Guide - Resolving Auto Layout Issues](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/ResolvingIssues/ResolvingIssues.html)