【Swift】ScrollView + AutoLayout (SnapKit) での実装メモ

# はじめに 
よくあるような縦方向・横方向の ScrollView の実装方法のメモ。
以下のような画面を実装します。
よければ参考にしてください。

![Simulator Screen Recording - iPhone SE (2nd generation) - 2021-09-26 at 01.08.56.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1203765/e3bc7452-c9e8-ae1a-acf8-6c30e25a86d5.gif)

【注意】
※ Viewはコードベースで作成しています。
※ レイアウトには [SnapKit](https://github.com/SnapKit/SnapKit) を使用しています。


# 環境
- Swift5
- Xcode13.0

# 実装手順 
順番に実装手順を記載していきます。
ここでは、色々省略して、最低限のコードのみ記載しています。
細かいことは、記事の最後に全体のコードを載せておりますので、そちらをご参照ください。

① ScrollView を生成する。

````swift
private let scrollView: UIScrollView = {
    let scrollView = UIScrollView()
    scrollView.isPagingEnabled = false
    scrollView.showsVerticalScrollIndicator = true  // 縦方向のスクロールバーをつけるかどうか。
    // !! 横方向のスクロールバーは以下で設定する。
    // scrollView.showsHorizontalScrollIndicator = true
    return scrollView
}()
````
② ScrollView 内でスクロールさせるViewを生成する。(本記事では、contentView とする)

````swift
private let contentView = UIView()
````

③ 実際に表示したい各Viewを生成していく。(本記事では、赤色のView, 青色のView, 黄色のView が該当)

````swift
private let contentView: UIView = {
    let view = UIView()
    return view
}()

private let redView: UIView = {
    let view = UIView()
    view.backgroundColor = .red
    return view
}()

private let blueView: UIView = {
    let view = UIView()
    view.backgroundColor = .blue
    return view
}()
````

④ contentsView に、③で生成した View を追加して、AutoLayoutをかけていきます。
縦方向のスクロールを前提に、レイアウトをかけていきます。

````swift
contentView.addSubview(redView)
contentView.addSubview(blueView)
contentView.addSubview(yellowView)

redView.snp.makeConstraints {
    $0.top.left.right.equalToSuperview()
    $0.height.equalTo(view.frame.height)
}
blueView.snp.makeConstraints {
    $0.top.equalTo(redView.snp.bottom)
    $0.left.right.equalToSuperview()
    $0.height.equalTo(view.frame.height)
}
yellowView.snp.makeConstraints {
    $0.top.equalTo(blueView.snp.bottom)
    $0.left.right.bottom.equalToSuperview()
    $0.height.equalTo(view.frame.height)
}
````
 
⑤ scrollView に contentsView を追加して、AutoLayoutをかけていきます。

````swift
scrollView.addSubview(contentView)

contentView.snp.makeConstraints {
    $0.width.equalTo(scrollView.frameLayoutGuide) // 縦方向にスクロールさせたい場合
    // !! 横方向にスクロールしたい場合は、height に対して制約をかけます。
    // $0.height.equalTo(scrollView.frameLayoutGuide)
    $0.edges.equalTo(scrollView.contentLayoutGuide)
}
````

⑥ ViewController の View に ScrollView を追加して、AutoLayoutをかければ、最小限の実装は完了します。

```swift
view.addSubview(scrollView)

scrollView.snp.makeConstraints {
    $0.edges.equalTo(view.safeAreaLayoutGuide)
}
```

⑦ あとは適宜、調整する。






### その他
横スクロールの際の、PageControl は以下で実装します。
(全体のコードを最後に記載しているのでそちらも合わせてご参照ください。)

![スクリーンショット 2021-09-26 3.58.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1203765/6d999d8c-0fbb-11c8-2c87-9808d0595faf.png)

① PageControl を生成する。

````swift
private let pageControl: UIPageControl = {
    let pageControl = UIPageControl()
    pageControl.pageIndicatorTintColor = .lightGray
    pageControl.currentPageIndicatorTintColor = .black
    pageControl.numberOfPages = 3  // ページ数。(画面でいうドットの数。)
    return pageControl
}()

````

② ScrollView の delegete を ViewController に設定する。

````swift
private lazy var scrollView: UIScrollView = {
    let scrollView = UIScrollView()
    scrollView.delegate = self
    return scrollView
}()
````
③ scrollViewDidScroll(_ scrollView:) の delegateメゾットから UIPageControlを更新する。

````swift
extension HorizontalViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        pageControl.currentPage = Int(scrollView.contentOffset.x / scrollView.frame.size.width)
    }
}
````



# 全体のコード
以下に、「はじめに」に添付した Gif のコードを記載します。
GitHubは[こちら](https://github.com/yusinoue1008/ScrollView-SnapKit-Sample)。

### 縦方向のスクロール

![Simulator Screen Recording - iPhone SE (2nd generation) - 2021-09-26 at 01.08.56 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1203765/de31af72-0455-6d01-4b9f-3077ed69d826.gif)


````VerticalViewController.swift
import UIKit
import SnapKit

final class VerticalViewController: UIViewController {
    
    private let scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.backgroundColor = .red
        return scrollView
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        return view
    }()
    
    private let redView: UIView = {
        let view = UIView()
        view.backgroundColor = .red
        return view
    }()
    
    private let blueView: UIView = {
        let view = UIView()
        view.backgroundColor = .blue
        return view
    }()
    
    private let yellowView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        return view
    }()
    
    
    override func viewDidLoad() {
        
        contentView.addSubview(redView)
        contentView.addSubview(blueView)
        contentView.addSubview(yellowView)
        scrollView.addSubview(contentView)
        view.addSubview(scrollView)
        
        redView.snp.makeConstraints {
            $0.top.left.right.equalToSuperview()
            $0.height.equalTo(view.frame.height)
        }
        blueView.snp.makeConstraints {
            $0.top.equalTo(redView.snp.bottom)
            $0.left.right.equalToSuperview()
            $0.height.equalTo(view.frame.height)
        }
        yellowView.snp.makeConstraints {
            $0.top.equalTo(blueView.snp.bottom)
            $0.left.right.bottom.equalToSuperview()
            $0.height.equalTo(view.frame.height)
        }
        contentView.snp.makeConstraints {
            $0.width.equalTo(scrollView.frameLayoutGuide)       // 縦方向の Scroll の場合は Width に合わせる
            $0.edges.equalTo(scrollView.contentLayoutGuide)
        }
        scrollView.snp.makeConstraints {
            $0.edges.equalTo(view.safeAreaLayoutGuide)
        }
    }
}

````

### 横方向のスクロール(チュートリアル画面風)

![Simulator Screen Recording - iPhone SE (2nd generation) - 2021-09-26 at 04.08.24.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1203765/070becf2-72ef-93ae-d3d4-d4ee05971df6.gif)

````HorizontalViewController.swift
import UIKit
import SnapKit

final class HorizontalViewController: UIViewController {
    
    private let headerLabel: UILabel = {
        let label = UILabel()
        label.text = "横方向の ScrollView\n(チュートリアル画面風)"
        label.numberOfLines = 0
        label.font = .systemFont(ofSize: 20)
        label.textAlignment = .center
        label.textColor = .black
        return label
    }()
    
    private lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.isPagingEnabled = true
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.delegate = self
        return scrollView
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        return view
    }()
    
    private let redView: UIView = {
        let view = UIView()
        view.backgroundColor = .red
        return view
    }()
    
    private let blueView: UIView = {
        let view = UIView()
        view.backgroundColor = .blue
        return view
    }()
    
    private let yellowView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        return view
    }()
    
    private let pageControl: UIPageControl = {
        let pageControl = UIPageControl()
        pageControl.pageIndicatorTintColor = .lightGray
        pageControl.currentPageIndicatorTintColor = .black
        pageControl.numberOfPages = 3
        return pageControl
    }()

    override func viewDidLoad() {
        
        contentView.addSubview(redView)
        contentView.addSubview(blueView)
        contentView.addSubview(yellowView)
        scrollView.addSubview(contentView)
        view.addSubview(headerLabel)
        view.addSubview(scrollView)
        view.addSubview(pageControl)
        
        redView.snp.makeConstraints {
            $0.top.bottom.left.equalToSuperview()
            $0.width.equalTo(view.frame.width)
        }
        blueView.snp.makeConstraints {
            $0.top.bottom.equalToSuperview()
            $0.left.equalTo(redView.snp.right)
            $0.right.equalTo(yellowView.snp.left)
            $0.width.equalTo(view.frame.width)
        }
        yellowView.snp.makeConstraints {
            $0.top.bottom.right.equalToSuperview()
            $0.left.equalTo(blueView.snp.right)
            $0.width.equalTo(view.frame.width)
        }
        contentView.snp.makeConstraints {
            $0.height.equalTo(scrollView.frameLayoutGuide)       // 横方向の Scroll の場合は height を frameLayoutGuide に合わせる
            $0.edges.equalTo(scrollView.contentLayoutGuide)
        }
        headerLabel.snp.makeConstraints {
            $0.top.left.right.equalTo(view.safeAreaLayoutGuide)
            $0.height.equalTo(100)
        }
        scrollView.snp.makeConstraints {
            $0.top.equalTo(headerLabel.snp.bottom)
            $0.left.right.equalTo(view.safeAreaLayoutGuide)
            $0.bottom.equalTo(pageControl.snp.top)
        }
        pageControl.snp.makeConstraints {
            $0.top.equalTo(scrollView.snp.bottom)
            $0.left.right.bottom.equalTo(view.safeAreaLayoutGuide)
            $0.height.equalTo(15)
        }
    }
}

extension HorizontalViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        pageControl.currentPage = Int(scrollView.contentOffset.x / scrollView.frame.size.width)
    }
}
````
# 参考にした記事
- [Xcode11の新機能でUIScrollViewのAutoLayoutを攻略](https://blog.shota-ito.com/2020/02/11/post-164/)
- [Xcode11のIBに追加されたContentLayoutGuideとFrameLayoutGuideについて](https://qiita.com/owen/items/2fab4a4482834b95e349)