PageViewControllerを使ってチュートリアル画面を実装する


作ろうとしているもののイメージ

https://qiita.com/Motchy_1204/items/5d4c281d7cc1c3c26365

スワイプではなくてボタンをタップして別の画面を表示する

少し違うのはここでは色を切り替えているだけだけど
- 今回は画面を切り替える
- 2画面目では画面内のボタンをタップしてページを切り替える

# 今回の仕組み

各画面ごとのViewControllerを用意してボタンタップすることでそのViewControllerを切り替える
そのためViewControllerにPageViewControllerを持たせる必要があるのでViewControllerにViewControllerを持たせるためのContainerViewを持たせる
次に各画面用のViewControllerを用意して、PageViewControllerによって画面遷移を行う

### ContainerViewとは
ViewControllerの中にViewControllerを配置することができるもの。
xibじゃなくてViewControllerを置きたい！という時に利用する。

### PageViewControllerとは
スワイプで用意したViewControllerに画面遷移ができる
画面遷移時にアニメーションを付けられる

# 手順１: UIPageViewControlerをViewControllerに持たせる

### StoryboardからViewControllerにUIPageViewControlerを持たせます
```
   流れ
    - ContainerViewとしてUIViewをViewControllerに持たせる
    - UIPageViewControllerを作る
    - ContainerViewとUIPageViewControllerを繋ぎEmbedを選択する
```


| UIPageViewControllerを追加 | Embedで繋ぐ| 完成 |
|-------|-------|-------|
| ![スクリーンショット 2020-03-29 10.08.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/3f69b067-2f5b-0068-d1ac-14398199edfc.png) | ![スクリーンショット 2020-03-29 10.10.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/9f152fcc-99f0-94c6-f57e-9310b4355433.png)| ![スクリーンショット 2020-03-29 10.10.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/5f3d1c9d-620e-8772-0588-00be809d9f0a.png)|

コードでこのPageViewControllerを取得していきます
childrenでそのViewControllerが持つViewControllerの配列を取得できるのでchildren.firstで今回ViewControllerがもつUIPageViewControllerを取得することができます
> An array of view controllers that are children of the current view controller.
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621452-children

<img width="773" alt="スクリーンショット 2020-03-29 10.21.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/8fe5a6ca-1c2a-d0d1-b7e0-10bd8e9feb42.png">

このViewControllerは各画面のコンテナになるので名前を変えて、それと今後どこかでこのViewControllerを取得する可能性もあるのでコードで取得できるようにしておきます

| 名前を変更 | コード取得できるように設定 |
|-------|-------|
| <img width="807" alt="スクリーンショット 2020-03-29 10.56.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/591bdd65-eb45-6585-eed4-eb4ef868fd25.png"> | <img width="262" alt="スクリーンショット 2020-03-29 10.56.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/3e473fc7-01f0-a4f1-85f1-ec7f5444b595.png"> |


# チュートリアルで表示する画面の作成
各画面用のViewControllerをファイルとstoryboard両方に作成
TutorialCotainerViewControllerと同じように、storyboardで作成したViewControllerをコードで取得できるようにStoryboard IDを設定しておきます

| FirstVCコード | FirstVC Storyboard |
|-------|-------|
| <img width="644" alt="スクリーンショット 2020-03-29 11.36.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/68f91781-34d8-794f-fece-90f4db84bb89.png"> | <img width="885" alt="スクリーンショット 2020-03-29 11.37.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/5e853745-a2f4-416c-0070-1ae49507067a.png"> |

| SecondVCコード | SecondVC Storyboard |
|-------|-------|
| <img width="850" alt="スクリーンショット 2020-03-29 11.39.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/dff52a40-37af-0906-6587-a0ca5e9595c1.png"> | <img width="889" alt="スクリーンショット 2020-03-29 11.37.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/2994cacb-1ff9-58f3-fb26-d80767b7d3d5.png"> |

| ThirdVCコード | ThirdVC Storyboard |
|-------|-------|
| <img width="456" alt="スクリーンショット 2020-03-29 11.36.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/956aa6f6-a80c-c539-2492-ff081e270d58.png"> | <img width="890" alt="スクリーンショット 2020-03-29 11.37.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/5fb61517-089a-ebb3-54db-1158ef7e1663.png"> |


# 完成
完成したコードとstoryboardです

SecondVCの時はNextボタンを非活性にして「次へ」ボタンタップでThirdVCへ遷移するようにしています
遷移自体はTutorialContainerVCが担うので、SecondVCでボタンがタップされたらクロージャーでTutorialVCへ通知して遷移させるようにしています

| Storyboard | TutorialContainerVC コード | FirstVC コード | SecondVC コード | ThirdVC コード |
|-------|
| <img width="1145" alt="スクリーンショット 2020-03-29 11.41.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/6183c0b0-4e01-de28-821a-2e48c0603488.png"> |


```
import UIKit

class TutorialContainerViewController: UIViewController {
    enum Page:Int {
        case first
        case second
        case third
    }
    
    // MARK: - IBOutlet
    @IBOutlet weak var nextButton: UIButton!
    
    // MARK: - private
    var currentPage = Page.first
    private lazy var pageViewController: UIPageViewController = {
        // このViewControllerが持つpageViewControllerを取得
        let pageVC = children.first as! UIPageViewController
        return pageVC
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil)
    }

    @IBAction func tappedButton(_ sender: UIButton) {
        if currentPage == .first {
            pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil)
            currentPage = .second
            nextButton.isEnabled = false
            return
        }
    }
}

extension TutorialContainerViewController {
    private func getFirst() -> FirstViewController {
        return storyboard!.instantiateViewController(identifier: "FirstViewController")
    }
    
    private func getSecond() -> SecondViewController {
        let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController")
        // SecondViewControllerでボタンがタップされた通知が飛んでくる
        // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない
        vc.selectedButtonToThird = {
            self.nextButton.isEnabled = true
            self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil)
        }
        return vc
    }
    
    private func getThird() -> ThirdViewController {
        return storyboard!.instantiateViewController(identifier: "ThirdViewController")
    }
}
```

```
import UIKit

class FirstViewController: UIViewController {
    
}
```

```
import UIKit

class SecondViewController: UIViewController {
    
    @IBOutlet weak var nextButtonToThird: UIButton!
    
    // クロージャーを設定
    var selectedButtonToThird: (() -> ())?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    @IBAction func tappedToThird(_ sender: UIButton) {
        // ボタンがタップされたらクロージャーで通知がいくようにする
        selectedButtonToThird?()
    }
}
```


```
import UIKit

class ThirdViewController: UIViewController {
    
}

```

# おまけ
## ページのアニメーションを横にスクロールするようなアニメーションにする
デフォルトではページをめくるようなアニメーションになっています
これを横にスクロールするようなアニメーションにする場合にはstoryboardでPageViewcontrollerを選択して
`Transition Style`を`Scroll`に変更することで実現できます

<img width="535" alt="スクリーンショット 2020-03-29 11.58.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602389/e0d8e601-fd94-7cb8-07f9-5e06a574b1d5.png">

## RootViewControllerを切り替える
チュートリアル画面を実装するようなアプリの場合初回起動判定で
初回と判定されればアプリ起動時にチュートリアル画面を表示
初回でないと判定されればトップ画面をアプリ起動時に表示とすることが多いです

```
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = makeRootVC()
        window?.makeKeyAndVisible()
        return true
    }

    // 初回起動のところは自分で判定処理を入れてください
    func makeRootVC() -> UIViewController {
        if 初回起動 {
            let tutorialContainerVC = storyboard?.instantiateViewController(identifier: "TutorialContainerViewController")
            // TutorialContainerViewControllerでdidSelectButtonToTopが呼ばれたらここに通知がきて {} 内が実行されrootViewControllerが変わることで画面が切り替わる
            tutorialContainerVC.didSelectButtonToTop = {
                self.window?.rootViewController = トップ画面のVC
            }
            return tutorialContainerVC
        }
        // 初回起動ではない場合
        return トップ画面のVC
    }
}
```

```
import UIKit

class TutorialContainerViewController: UIViewController {
    enum Page:Int {
        case first
        case second
        case third
    }
    
    // MARK: - IBOutlet
    @IBOutlet weak var nextButton: UIButton!
    
    // MARK: - internal
    var didSelectButtonToHome: (() -> Void)? ← NEW
    
    // MARK: - private
    var currentPage = Page.first
    private lazy var pageViewController: UIPageViewController = {
        // このViewControllerが持つpageViewControllerを取得
        let pageVC = children.first as! UIPageViewController
        return pageVC
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil)
    }

    @IBAction func tappedButton(_ sender: UIButton) {
        if currentPage == .first {
            pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil)
            currentPage = .second
            nextButton.isEnabled = false
            return
        }
        
        if currentPage == .third {
            didSelectButtonToHome?()  ← NEW
        }
    }
}

extension TutorialContainerViewController {
    private func getFirst() -> FirstViewController {
        return storyboard!.instantiateViewController(identifier: "FirstViewController")
    }
    
    private func getSecond() -> SecondViewController {
        let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController")
        // SecondViewControllerでボタンがタップされた通知が飛んでくる
        // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない
        vc.selectedButtonToThird = {
            self.nextButton.isEnabled = true
            self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil)
        }
        return vc
    }
    
    private func getThird() -> ThirdViewController {
        return storyboard!.instantiateViewController(identifier: "ThirdViewController")
    }
}

```

## ナビゲーションコントローラーを使っている場合
*StoryboardでTutorialContainerViewControllerもトップ画面VCもNavigationControllerと紐づいている、NavigationControllerを持っている場合

```
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = UINavigationController(rootViewController: makeRootVC)
        window?.makeKeyAndVisible()
        return true
    }

    // 初回起動のところは自分で判定処理を入れてください
    func makeRootVC() -> UIViewController {
        if 初回起動 {
            return storyboard?.instantiateViewController(identifier: "TutorialContainerViewController")
        }
        // 初回起動ではない場合
        return トップ画面のVC
    }
}
```

```
import UIKit

class TutorialContainerViewController: UIViewController {
    enum Page:Int {
        case first
        case second
        case third
    }
    
    // MARK: - IBOutlet
    @IBOutlet weak var nextButton: UIButton!
    
    // MARK: - private
    var currentPage = Page.first
    private lazy var pageViewController: UIPageViewController = {
        // このViewControllerが持つpageViewControllerを取得
        let pageVC = children.first as! UIPageViewController
        return pageVC
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil)
    }

    @IBAction func tappedButton(_ sender: UIButton) {
        if currentPage == .first {
            pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil)
            currentPage = .second
            nextButton.isEnabled = false
            return
        }
        
        if currentPage == .third {
            // NavigationControllerによって管理されているViewControllersを引数に渡した特定のViewControllerに置き換える(リセットされる)
            navigationController?.setViewControllers([トップ画面VC], animated: true)
        }
    }
}

extension TutorialContainerViewController {
    private func getFirst() -> FirstViewController {
        return storyboard!.instantiateViewController(identifier: "FirstViewController")
    }
    
    private func getSecond() -> SecondViewController {
        let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController")
        // SecondViewControllerでボタンがタップされた通知が飛んでくる
        // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない
        vc.selectedButtonToThird = {
            self.nextButton.isEnabled = true
            self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil)
        }
        return vc
    }
    
    private func getThird() -> ThirdViewController {
        return storyboard!.instantiateViewController(identifier: "ThirdViewController")
    }
}
```

間違いがあったらすみません


# 参考
https://qiita.com/Motchy_1204/items/5d4c281d7cc1c3c26365
https://qiita.com/ninoko1995/items/5498c1e821ba3e94bdd6
https://qiita.com/Kyoya1123/items/588206357cf9c6492047
https://qiita.com/koji-nishida/items/b901f47255ddcfbaa927