[Xcode]UIScrollViewを最低限動かすための手順・やり方

iOS開発の学習をしています。

最近学んだ中で、特にUIScrollViewの扱いについてはかなり苦労しました。
そのUIScrollViewについて`最低限動かす`ということに焦点を絞って備忘録的に書いています。

「なんだか良く分からないけど、誰かの記事見てそのまま書いて、動いたからオッケー！」
とはならないように、初学者なりに理屈を考えながら実装を試みました！
足りない部分も多々ありますが、私と同じような初学者の方々の参考になれば幸いです。

## 概要
新規プロジェクトを作成後、storyboradを使用しUIScrollViewをスクロールするところまでを実践する。


### 環境
- macOS Catalina 10.15.7
- Xcode - 11.3.1

### 完成品
 
[![Image from Gyazo](https://i.gyazo.com/02b64029ea25cf3142681fd838e31c24.gif)](https://gyazo.com/02b64029ea25cf3142681fd838e31c24)

#### 内容
- UIScrollView
- UIView
- UILabel * 2

## 実践
### ①ViewにScrollViewを設置
Main.storyboard→View Controller→Viewを選択し、
ViewにScrollViewを設置する。

![scroll-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/785e2978-f970-1905-1291-7e02207eeb0a.png)

後ほど制約をつけるのでどこでも良いです。ちょっと大きめにしておいた方が見やすいかも。

### ②ScrollViewに制約をつける
先ほど設置したScrollViewにAutoLayoutで制約を加えていきます。
Add New Constraintsから設定していきます。
ScrollViewのサイズを決める制約となります。
親Viewに対しての余白を決めることで、サイズを決めています。

数字はなんでも良いですが今回は上下に50,左右に0とします。

![scroll-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/7d5612d5-5cd1-0444-b5cf-39ec571e1e0d.png)

Add 4 Constraintsを選択すると、ScrollViewに以下の４つの制約が追加されます。

```
- Safe Area.trailing = Scroll View.trailing
- Safe Area.bottom = Scroll View.bottom + 50
- Scroll View.leading = Safe Area.leading
- Scroll View.top = Safe Area.top + 50
```

また、ScrollViewに対し赤いラインが表示され"Scrollable Content Size Ambiguity"という制約エラーが出ます。

![scroll-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/ad8ea45e-18df-2e54-6ffd-f8a80c940ed6.png)


![scroll-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/6b7009aa-2d14-ce3e-d92f-8bdb31459bb0.png)

これは直訳の通り"スクロール可能なコンテンツのサイズが曖昧"という警告です。

#### 補足：Scrollable Content Size Ambiguityとは？
では、このエラーは一体なんなのか？ということを考えていきます。

エラー文によると、
>The scrollable size of a UIScrollView is computed automatically based upon the constraints of its subviews.
In order to fully define the scrollable content width and height, there needs to be constraints 
touching all edges (leading, trailing, top, and bottom) of the scroll view.

> 訳：UIScrollViewのスクロール可能なサイズは、そのサブビューの制約に基づいて自動的に計算されます。
スクロール可能なコンテンツの幅と高さを完全に定義するには、制約が必要です。

という内容が記載されています。
どうやらエラー文で指摘を受けた`スクロール可能なコンテンツのサイズ`を定義するために`サブビューを制約`する必要があるようです。

#### 補足2：サブビューとは？
Xcodeのレイアウトはオブジェクトを上に重ねていくような構造になっており、ユーザから見て手前にあるビューをサブビュー(subview)、奥にあるビューをスーパービュー(superview)というようです。

![scroll-debug.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/c30e4d13-9dc6-33e0-78a6-97fb7c4ff641.png)

こんな感じ。(画像は完成品のレイアウト)

#### エラーは一旦スルーで
上記の理由から、現状ScrollViewはサブビューを持っていないのでエラーを解消することは出来ません。
ScrollViewの中にContentsViewなどを設置してからの対応となります。

### ③ScrollViewの中にViewを設置
![scroll-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/ba19cdd7-87b5-a0a9-21b5-221340c6953e.png)

ただのViewです。
分かりやすくするために、ScrollViewを水色、新たに設置したViewを灰色にしています。

このViewですが、名前は慣例的に`"ContentsView"`などとするのが良いらしいです。
本記事でも、以後ContentsViewと記載します。

### ④ContentsViewとContentLayoutGuideに制約をつける
こちらの記事がとても分かりやすく、大変参考になりました。
[【Swift】Align制約の使い方。複数部品を整列する制約を追加する。(Swift 2.1、XCode 7.2)](https://hajihaji-lemon.com/swift/post-6366/)

![scroll-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/0d917c10-bd4f-240b-72d8-4d7ffdafc6e0.png)


コマンドキー押して２つ同時選択し、AutolayoutのAlignを選択。
Add New Alignment Constraintsから上下左右を固定します。

上４つを`0`にしてチェックを入れます。

- Leading Edges <br>部品の左端から他の部品の左端までの水平方向の距離を指定
- Trailing Edges <br>部品の右端から他の部品の右端までの水平方向の距離を指定
- Top Edges <br>部品の上端から他の部品の上端までの垂直方向の距離を指定
- Bottom Edges <br>部品の下端から他の部品の下端までの垂直方向の距離を指定

以下の制約が追加されます。

```
- ContentsView.top = Content Layout Guide.top
- ContentsView.leading = Content Layout Guide.leading
- ContentsView.bottom = Content Layout Guide.bottom
- ContentsView.trailing = Content Layout Guide.trailing
```

この制約手法は、複数のオブジェクトを選択した場合のみ使える制約となっており、一つしか選択していない状態だと、チェックを入れることが出来ません。

ではこれらはどういう制約なのか考えてみます。
`Leading Edges 部品の左端から他の部品の左端までの水平方向の距離を指定`とあるように２つの部品の距離を指定するものです。

例えばLeading Edgesを0にすることより、
**(部品A)ContentLayoutGuide**の左端と、**(部品B)ContentsView**の左端の
距離を指定した"0"にすることができる。
要するにくっつくと言うことです。

※あくまで現時点の自分の理解では、オブジェクトの開始位置を、他のオブジェクトに依存して決める設定というイメージ。

４箇所指定することで、ContentLayoutGuideとContentsViewの４隅の位置を固定する制約となっている。


### ⑤FrameLayoutGuideとContentsViewの横幅を合わせる
上記④ではContentLayoutGuideに合わせてContentsViewの位置を設定したが、
ContentsView自身のサイズを決めていないので、こちらも設定していきます。(当然エラー状態となっている)

はじめにContentsViewの横幅を設定します。
高さはContentsView自身で定める必要があるが、横幅はScrollViewの枠に合わせる形が良いと思われます。(※一般的な縦のみスクロールの場合)

ContentsViewからcontrolキーを押しながらFrameLayoutGuideへドラッグし、Equal Widthsを選択。（青いガイドラインが出る)

![scroll-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/46ee5b3b-595e-83c8-3565-09db15dd7040.png)

追加された制約

```
- ContentsView.width = 0.57971 × Frame Layout Guide.width
  (数値は現在のContentsViewのサイズにより変動)
```

この時、元々のContentsViewの横幅を元に上記制約が作られてしまいます。
直すには制約の`Size inspector`から`Multiplier`を**1**に設定することで、ContentsViewをScrollViewの横幅目一杯に広げることができます。

![scroll-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/f2569bc2-e86f-9ee7-1eb3-7463632a39d1.png)

### ⑥ContentsViewの高さについて制約をつける
続いて高さを決めていきます。
ContentsViewを選択し、AddNewConstraintsへ。
上の余白を0、高さを1000とした。

![scroll-9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/21056bd0-6313-beb3-a4f0-a68ec21cdede.png)

追加された制約

```
- ContentsView.top = Frame Layout Guide.top
```
これに加えてContentsView自身にも`height = 1000`という制約が追加され、これで高さ&位置を確定することが出来ます。

##### **これで制約エラーは解消されます！**
ContentsViewを選択した状態でスクロールしてみてください。
動くハズです！

### ⑦Labelを配置する
ここからはおまけです。
ContentsViewになんでも好きなものを配置して動かしている気分を味わっていきましょう。

![scroll-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662505/b5956468-8c6b-72dc-9896-a825ee66be3e.png)

### 完成！
これで冒頭の完成品となりました！

## 後語り
いかがだったでしょうか？
まだまだ理解出来ていない部分も多いので、学習を進めていく過程で随時更新していきたいと考えています。

また、`ContentLayoutGuide`と`FrameLayoutGuide`に関しては全然理解が追いついていないので、今後の課題と思ってしっかり学習していこうと思います！