【徹底解説】UIScrollViewクラス　その1

#UIScrollViewクラスとは
UIScrollView（以下，SV）は，画面に触れた指などの動きを追跡することで，画面サイズよりもはみ出たコンテンツを表示させるためのUI部品です。 UITableViewやUITextViewも画面からはみ出た範囲をスクロールさせることができますが，その理由は，SVがこれらのスーパークラスであるためです。

#環境

- XCode Version 10.2.1
- Swift 5.0

# 第1章　とりあえずスクロールさせる

ここではスクロールするコンテンツをstoryboardを用いて作製してみます。実践には程遠いですが何事も基本が大切なので基礎固めしていきましょう。でも本当に固まってはいけません。

###SVの配置
ナビゲータエリアからMain.storyboardを選択し，Objectライブラリから「Scroll View」を選びます。それを画面（View）上にドロップします。ドロップしたSVを画面全体に広げます。もちろん必ずしも画面全体に広げる必要はありません。用途に応じてサイズを変更させてください。ここでは練習のため画面全体に広げているだけです。

<img width="1183" alt="ScrollView_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/c9ebcc5d-7e71-fa95-6652-cab5d6e8f303.png">

###コンテンツビューの配置

実際にスクロールさせる部品であるコンテンツビュー（contents view）を配置します。コンテンツビューにはUIViewを用いることが多いと思います。Objectライブラリから「View」を選択し，SV上にドロップします。以後分かりやすいようにViewの名前を「contentsView」に変更します。

大きさと位置を決めましょう。右上にある「Show the Size inspector」にある「width」と「height」にぞれぞれ「800」と「1200」を設定します。この```contentsView```を上下左右に青い点線が出るところに配置します。もし上手くいかなければ，「Show the Size inspector」の「X」と「Y」にそれぞれ「-193」と「-152」を入力してください（iPhoneXRを選択していること）。もちろんこれらも用途に合わせて自由に設定してください。

コンテンツビューだけではスクロールしているかどうかの判別が付かないのでUILabelを配置させましょう。Objectライブラリから「label」を選び，```contentsView```上にドロップさせ，上下左右に青い点線が出るところに配置させます。

<img width="1005" alt="ScrollView2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/a9dee787-a26c-c903-d940-f55fdb49dbb1.png">

###コンテンツビューのレイアウト

まだ各UI部品の位置関係についても何も制約をかけていませんでしたので制約をかけていきます。View Controllerを選択してある状態で画面右下の「Resolve Auto Layout issues」を押します。その中にある「All Views in Container」欄の「Add Missing Constraints」を押します。すると下図のように制約がかかると思います。

<img width="1005" alt="ScrollView_3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/bcefb419-c77c-23d5-6599-7fb2654ac7c4.png">

お疲れさまです。ここまできたら一旦ビルドしてみて下さい。ビルド後，画面をマウスなどで動かして見てください。おそらく動かないはずです。衝撃的ですよね。ここまで説明しておいて動かないなんて。SV上にはそれより大きな```contentsView```を配置したのでスクロールできても良さそうですが何故かできせん。Appleが悪いわけではありません。制約のかけ方が間違っているからです。下図の青色の点線で囲んだところをよく見てください。

<img width="329" alt="ScrollView_4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/6e757859-647f-fab1-c41c-06c652a8d626.png">


例えばSVのbottomの位置をみると```contentsView```から152下がった位置で制約がかかっています。上下左右ともSVと```contentsView```の位置はガチガチに制約がかかっている状態です。この状態ではスクロールはできません。

それではスクロールができるように制約をかけ直しましょう。まず上の青い点線で囲んだ「Constraints」を消します。次に，contentsViewを選択し，画面右下にある「Add New Constraints」を押し，4つとも「0」を入力します。そして最後に「Add 4 Constraints」を押し，ビルドしてください。今度はスクロールできるはずです。

![ScrollView_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/44377438-e930-adfe-2299-1879bde4ed1b.gif)

スクロールできた理由が大切です。SVの上下左右の位置と```contentsView```の上下左右の位置を一致させたためです。これによって，スクロールできる範囲は，左方向に関しては```contentsView```の左端がSVの左端に一致するまでとなりました。その他の位置も同様です。

# 第2章　コードでやってみる

SVの配置のみstoryboard上で行い，上記で行ったコンテンツビューの配置などはコードでやってみましょう。

###Outlet接続

SV以外のUIは全て削除し，SVをscrollViewという名前でOutlet接続させます。

<img width="691" alt="ScrollView_6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/cb218810-eed8-9641-d086-347bd342754d.png">

###コンテンツビューの作製

コンテンツビューとその中身を作っていきましょう。以下がコード全体になります。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {
 
    @IBOutlet weak var scrollView: UIScrollView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        configureSV()
    }
    
    func createLabel(contentsView: UIView) -> UILabel {
        
        // labelを作る
        let label = UILabel()
        
        // labelの座標をcontentsViewの中心にする
        let labelX = contentsView.center.x
        let labelY = contentsView.center.y
        label.frame = CGRect(x: labelX, y: labelY, width: 95, height: 50)
        
        label.text = "Label"
        return label
    }
    
    func createContentsView() -> UIView {
        
        // contentsViewを作る
        let contentsView = UIView()
        contentsView.frame = CGRect(x: 0, y: 0, width: 800, height: 1200)
        
        // contentsViewにlabelを配置させる
        let label = createLabel(contentsView: contentsView)
        contentsView.addSubview(label)
        
        return contentsView
    }
    
    func configureSV() {
        
        // scrollViewにcontentsViewを配置させる
        let subView = createContentsView()
        scrollView.addSubview(subView)
        
        // scrollViewにcontentsViewのサイズを教える
        scrollView.contentSize = subView.frame.size
    }

}

```

それでは細かく見ていきましょう。

####```func createLabel(contentsView: UIView) -> UILabel```

ラベルを生成するメソッドです。

```swift
let label = UILabel()
// labelの座標をcontentsViewの中心にする
let labelX = contentsView.center.x
let labelY = contentsView.center.y
label.frame = CGRect(x: labelX, y: labelY, width: 95, height: 50)
label.text = "Label"
return label
```
引数である```contentsView```の中心に```label```が配置するように設定しています。```return```でその```label```を返します。

####```func createContentsView() -> UIView ```

コンテンツビューを生成するメソッドです。

```swift
let contentsView = UIView()
contentsView.frame = CGRect(x: 0, y: 0, width: 800, height: 1200)
// contentsViewにlabelを配置させる
let label = createLabel(contentsView: contentsView)
contentsView.addSubview(label)
return contentsView
```

####```func configureSV()```

SVの設定を行うメソッドです。

```swift
// scrollViewにcontentsViewを配置させる
let subView = createContentsView()
scrollView.addSubview(subView)
// scrollViewにcontentsViewのサイズを教える
scrollView.contentSize = subView.frame.size
```

スクロールさせるためにはSVにコンテンツビューの大きさを教える必要があります。SVの```contentSize```プロパティーにその大きさを設定することでSVにコンテンツビューの大きさを教えることができます。

これでビルドすると第1章と同様にスクロールさせることができると思います。ただ現状では，初期の「label」の位置が右下付近によっています。上下左右の中央にずらしたいですが，初期位置に関しては別の章で説明します。

# 第3章　横スクロールさせる

第1，2章だけではあまり面白みがありません。SVを使用する時は，横か縦方向にスクロールさせることが多いと思います。この章では横スクロールに焦点を当て，もうすこし実践的にしましょう。

###スクロールで色が変わる画面を作る

次のようにスクロールさせることでページごとに色が変わるのアプリケーションを作りましょう。

![Scroll_color.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/bc3730c7-4d3c-b2dc-b924-162612a69f0e.png)

スクロールビューのみstoryboardを使用して，それ以外はコードで実装していきます。任意ですが，スクロールビューの制約はSafeAreaに対して行います。全体のコードです。

```swift:ViewController.swift

import UIKit

class ViewController: UIViewController {

    let numberOfPages = 5
    let colors: [UIColor] = [.yellow, .gray, .red, .blue, .brown]
 
    @IBOutlet weak var scrollView: UIScrollView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        configureSV()
    }

    func createPages(page: Int) -> UIView {
        let pageView = UIView()
        let pageSize = scrollView.frame.size
        let positionX = pageSize.width * CGFloat(page)
        let position = CGPoint(x: positionX, y: 0)
        pageView.frame = CGRect(origin: position, size: pageSize)
        pageView.backgroundColor = colors[page]

        return pageView
    }

    func createContentsView() -> UIView {
        let contentsView = UIView()
        let contentsWidth = scrollView.frame.width * CGFloat(numberOfPages)
        let contentsHeight = scrollView.frame.height
        contentsView.frame = CGRect(x: 0, y: 0, width: contentsWidth, height: contentsHeight)

        for i in 0 ..< numberOfPages {
            let pageView = createPages(page: i)
            contentsView.addSubview(pageView)
        }

        return contentsView
    }

    func configureSV() {
        let contentsView = createContentsView()
        scrollView.addSubview(contentsView)
        scrollView.contentSize = contentsView.frame.size
    }

}
```

細かく見ていきましょう。

####インスタンスプロパティー

　```numberOfPages```：ページ数
　```colors```：色の設定

####```func createPages(page: Int) -> UIView  ```

ページを生成するメソッドです。

```swift
let pageView = UIView()
let pageSize = scrollView.frame.size
let positionX = pageSize.width * CGFloat(page)
let position = CGPoint(x: positionX, y: 0)
pageView.frame = CGRect(origin: position, size: pageSize)
pageView.backgroundColor = colors[page]
return pageView
```
ページの幅は```scrollView```と同じにしています。引数```page```は現在のページの番号です。ページがスクロールした分だけページの位置がずれ，その値を```positionX```に格納します。pageの背景色は```colors```に格納されている色を順に取り出して 
```backgroundColor```で設定します。

####```func configureSV()```
前章と同じでSVの設定を行います。

さて，これでビルドすると以下のようにスクロールができると思います。

![Scroll_color.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399323/ee3f0a49-7761-6a53-89fb-3c0cd7d5f6eb.gif)

###スクロールビューの設定

ここではスクロールビューにどんな設定ができるかを見てみましょう。すべて```Bool```型で設定します。

|インスタンスプロパティー名|```true```の時の内容|デフォルト値|
|:--------|:--------|:--------|
|```isScrollEnabled```|スクロール可能|```true```|
|```isDirectionalLockEnabled```|最初にスクロールさせた方向（水平か垂直）のみスクロール可能|```false```|
|```isPagingEnabled```|1ページずつスクロールする|```false```|
|```scrollsToTop```|ステータスバーをタップすると一番上までスクロールする|```true```|
|```bounces```|スクロールがコンテンツの一番端に到達すると跳ね返る（バウンドする）|```true```|
|```alwaysBounceVertical```|コンテンツがスクロールビューよりも小さい場合でも常に垂直方向のバウンドを許可する。ただし```bounces=true```であることも必要|```false```|
|```alwaysBounceHorizontal```|コンテンツがスクロールビューよりも小さい場合でも常に水平方向のバウンドを許可する。ただし```bounces=true```であることも必要|```false```|

例えば，ページ単位でスクロールするには以下のように設定します。

```swift
let scrollView = UIScrollView()
scrollView.isPagingEnabled = true
```

記事が長くなってきたのでここで一旦区切ります。第4章以降は別記事で説明します。お疲れ様でした。

# 続編

【徹底解説】UIScrollViewクラス　その2（執筆中）

# 参考文献
詳細! Swift iPhoneアプリ開発入門ノート 著者：大重 美幸　出版社: ソーテック社

---
ご意見等あれば連絡ください。