【swift】pageViewControllerでページ遷移時に行いたい処理の記述場所は？

## 前置き
スワイプによるページ遷移、ボタン押下によるページ遷移、までは実装できたものの、**スワイプによるページ遷移時のtopviewのデザインの変更ができない**とつまずいた人間の、備忘録。

## 環境
xcode 11.3
swift 5.1.3

## 実現したい機能
- スワイプでページ遷移 ： **完了**
- ボタン押下でページ遷移 ： **完了**
- ページ遷移に従い、ページ遷移しない部分のデザインの変更　： **これ！！！**

## 現状

### ファイル構成

- TopViewController.swift <- 元となるViewController
- PageViewController.swift <- page遷移機能をまとめたViewController
- View1Controller.swift
- View2Controller.swift 
- View3Controller.swift

### storybord

- 元となるViewControllerが存在
- その中に、3つのボタンと、ページのタイトルラベル、そしてcontainerViewが存在
- containerViewの先はpageViewControllerとなっている。
- 遷移先のページとして、viewControllrが3つ(view1,view2,view3)存在している。
- 各viewには、それぞれtopView,view1,view2,view3の**storybordID**をつけている

<img width="617" alt="スクリーンショット 2020-01-12 20.14.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270898/455c329e-64f8-d641-fc9f-5aeb6b078231.png">

###  コード

```swift:TopViewController.swift
import UIKit

class TopViewController: UIViewController  {
    // pageのタイトル
    @IBOutlet weak var pageTitle: UILabel!
    
    // buttons
    @IBOutlet weak var toView1Btn: boundButton!
    @IBOutlet weak var toView2Btn: boundButton!
    @IBOutlet weak var toView3Btn: boundButton!

    // toView1Btn押下時
    @IBAction func toView1Action(_ sender: Any) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "view1") as! View1Controller
        pageViewController!.setViewControllers([vc], direction: .forward, animated: false, completion: nil)
        toView1Design()
    }

    // toView2Btn押下時
    @IBAction func toView2Action(_ sender: Any) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "view2") as! View2Controller
        pageViewController!.setViewControllers([vc], direction: .reverse, animated: false, completion: nil)
        toView2Design()
    }
    
    // toView3Btn押下時
    @IBAction func toView3Action(_ sender: Any) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "view3") as! View3Controller
        pageViewController!.setViewControllers([vc], direction: .reverse, animated: false, completion: nil)
        toView3Design()
    }
    
    // 画面遷移用
    var pageViewController: UIPageViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // 最初はview1を出しとく
        toView1Design()
    }
}

// デザイン変更用関数のまとめ
extension TopViewController {
    // view1に遷移するとき呼ばれる
    func toView1Design() {
        // pageTitle変更
        pageTitle.text = "view1"
        
        // ボタンの色変更(選ばれているボタンを赤色に、それ以外は灰色に)
        toView1Btn.setTitleColor(UIColor.red, for: [])
        toView2Btn.setTitleColor(UIColor.lightGray, for: [])
        toView3Btn.setTitleColor(UIColor.lightGray, for: [])
        
    }

    // view２に遷移するとき呼ばれる
    func toView2Design() {
        // pageTitle変更
        pageTitle.text = "view2"
        
        // ボタンの色変更(選ばれているボタンを赤色に、それ以外は灰色に)
        toView1Btn.setTitleColor(UIColor.lightGray, for: [])
        toView2Btn.setTitleColor(UIColor.red, for: [])
        toView3Btn.setTitleColor(UIColor.lightGray, for: [])
        
    }
    
    // view3に遷移するとき呼ばれる
    func toView3Design() {
        // pageTitle変更
        pageTitle.text = "view3"
        
        // ボタンの色変更(選ばれているボタンを赤色に、それ以外は灰色に)
        toView1Btn.setTitleColor(UIColor.lightGray, for: [])
        toView2Btn.setTitleColor(UIColor.lightGray, for: [])
        toView3Btn.setTitleColor(UIColor.red, for: [])
        
    }
}
```

```swift:PageViewController.swift
import UIKit

class PageViewController: UIPageViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setViewControllers([getView1()], direction: .forward, animated: true, completion: nil)
        self.dataSource = self
    }


    // それぞれ、viewControllerを返す関数
    func getView1() -> View1Controller {
        return storyboard!.instantiateViewController(withIdentifier: "view1") as! View1Controller
    }

    func getView2() -> View2Controller {
        return storyboard!.instantiateViewController(withIdentifier: "view2") as! View2Controller
    }

    func getView3() -> View3Controller {
           return storyboard!.instantiateViewController(withIdentifier: "view3") as! View3Controller
       }

    func getTop() -> TopViewController {
        return storyboard!.instantiateViewController(withIdentifier: "topView") as! TopViewController
    }
}

//datasource用の関数まとめ
extension PageViewController : UIPageViewControllerDataSource {
    // 現在表示されているページの、Beforeに位置する、つまり左側に位置するviewを呼び出す関数
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        print(viewController)

        if viewController.isKind(of : View3Controller.self) {
            // 3の時 -> 2
            return getView2()
        }

        if viewController.isKind(of : RepoViewController.self) {
            // 2の時 -> 1
            return getView1()
        }

        if viewController.isKind(of : CalendarViewController.self) {
            // 1の時 -> 3
            return getView3()
        }

        return nil
    }

    // 現在表示されているページの、Afterに位置する、つまり右側に位置するviewを呼び出す関数
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        print(viewController)

        if viewController.isKind(of : CalendarViewController.self) {
            // 1の時 -> 2
            return getView2()
        }

        if viewController.isKind(of : RepoViewController.self) {
            // 2の時 -> 3
            return getView3()
        }

        if viewController.isKind(of : UserViewController.self) {
            // 3の時 -> 1
            return getView1()
        }

        return nil
    }
}

```

### 課題

スワイプによるページ遷移に伴うTopViewController上のデザインの変更メソッドを呼ぶ場所がわからない。
datasource用の関数が呼ばれるタイミングは、現在表示されているviewcontrollerとずれている。

## 解決策

### 検索結果

調べた結果、delegateメソッドでスワイプしたタイミングを検知してくれるメソッドがあるらしい。これを使う。
> [UIPageViewControllerでページ遷移時に処理したいことを書くときのTip](https://qiita.com/freddi_/items/3d6aea5c8c93f5638834)
>[Apple Developer : pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)](https://developer.apple.com/documentation/uikit/uipageviewcontrollerdelegate/1614090-pageviewcontroller)

### 実装

```swift:PageViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setViewControllers([getView1()], direction: .forward, animated: true, completion: nil)
        self.dataSource = self
        // ここから追記
        self.delegate = self
        // ここまで追記
    }

// 以下を追記
extension PageViewController : UIPageViewControllerDelegate{
    // スワイプによるページ遷移が行われたときに呼ばれるメソッド
    func pageViewController(_ pageViewController: UIPageViewController,
         didFinishAnimating finished: Bool,
         previousViewControllers: [UIViewController],
         transitionCompleted completed: Bool){
        
        // 現在のviewcontrollerを取得
        let currentVC = pageViewController.viewControllers![0]

        // topViewControllerを取得
        let topView = getTop()

        // デザインを変更処理
        if currentVC!.isKind(of : View1Controller.self) {
            topView.toView1Design()
        }
        
        if currentVC!.isKind(of : View2Controller.self) {
            topView.toView2Design()
        }
        
        if currentVC!.isKind(of : View3Controller.self) {
            topView.toView3Design()
        }
    }
    
}

```

### 課題２
しかしながらこのコードでは、デザイン変更関数が呼ばれた時に、toView1Btnがない、と言ったエラーが発生する。
pageViewController上でデザイン変更関数のみを呼び出しており、TopViewController上で行われている変数設定等を行っていないからだ。

## 解決策2

### 検索結果2
どうやら、pageVioewControllerをTopViewController内部で設定する方法もあるらしい。
この方法を用いれば、上記のエラーも発生しなくなるはず。

>[qiita : 【Swift】UIPageViewControllerとContainerViewを利用してタップで画面遷移](https://qiita.com/Motchy_1204/items/5d4c281d7cc1c3c26365)
>[]()

### 実装2

PageViewController.swiftを削除し、TopViewController.swiftに書き足し。

```swift:TopViewController.swift
import UIKit

class TopViewController: UIViewController  {
    // pageのタイトル
    @IBOutlet weak var pageTitle: UILabel!
    
    // buttons
    @IBOutlet weak var toView1Btn: boundButton!
    @IBOutlet weak var toView2Btn: boundButton!
    @IBOutlet weak var toView3Btn: boundButton!

    // toView1Btn押下時
    @IBAction func toView1Action(_ sender: Any) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "view1") as! View1Controller
        pageViewController!.setViewControllers([vc], direction: .forward, animated: false, completion: nil)
        toView1Design()
    }

    // toView2Btn押下時
    @IBAction func toView2Action(_ sender: Any) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "view2") as! View2Controller
        pageViewController!.setViewControllers([vc], direction: .reverse, animated: false, completion: nil)
        toView2Design()
    }
    
    // toView3Btn押下時
    @IBAction func toView3Action(_ sender: Any) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "view3") as! View3Controller
        pageViewController!.setViewControllers([vc], direction: .reverse, animated: false, completion: nil)
        toView3Design()
    }
    
    // 画面遷移用
    var pageViewController: UIPageViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // ここから追加
        // PageViewControllerの設定
        pageViewController = children.first! as? UIPageViewController
        pageViewController!.setViewControllers([getView１()], direction: .forward, animated: true, completion: nil)
        pageViewController!.dataSource = self
        pageViewController!.delegate = self
        // ここまで追加

        // 最初はview1を出しとく
        toView1Design()
    }
}

// デザイン変更用関数のまとめ
extension TopViewController {
    // view1に遷移するとき呼ばれる
    func toView1Design() {
        // pageTitle変更
        pageTitle.text = "view1"
        
        // ボタンの色変更(選ばれているボタンを赤色に、それ以外は灰色に)
        toView1Btn.setTitleColor(UIColor.red, for: [])
        toView2Btn.setTitleColor(UIColor.lightGray, for: [])
        toView3Btn.setTitleColor(UIColor.lightGray, for: [])
        
    }

    // view２に遷移するとき呼ばれる
    func toView2Design() {
        // pageTitle変更
        pageTitle.text = "view2"
        
        // ボタンの色変更(選ばれているボタンを赤色に、それ以外は灰色に)
        toView1Btn.setTitleColor(UIColor.lightGray, for: [])
        toView2Btn.setTitleColor(UIColor.red, for: [])
        toView3Btn.setTitleColor(UIColor.lightGray, for: [])
        
    }
    
    // view3に遷移するとき呼ばれる
    func toView3Design() {
        // pageTitle変更
        pageTitle.text = "view3"
        
        // ボタンの色変更(選ばれているボタンを赤色に、それ以外は灰色に)
        toView1Btn.setTitleColor(UIColor.lightGray, for: [])
        toView2Btn.setTitleColor(UIColor.lightGray, for: [])
        toView3Btn.setTitleColor(UIColor.red, for: [])
        
    }
}


// ここから下全部追記
// view取得系の関数まとめ
extension TopViewController {
    // それぞれ、viewControllerを返す関数
    func getView1() -> View1Controller {
        return storyboard!.instantiateViewController(withIdentifier: "view1") as! View1Controller
    }

    func getView2() -> View2Controller {
        return storyboard!.instantiateViewController(withIdentifier: "view2") as! View2Controller
    }

    func getView3() -> View3Controller {
           return storyboard!.instantiateViewController(withIdentifier: "view3") as! View3Controller
       }

    func getTop() -> TopViewController {
        return storyboard!.instantiateViewController(withIdentifier: "topView") as! TopViewController
    }
}

// datasource用の関数まとめ
extension TopViewController : UIPageViewControllerDataSource {
    
    // 現在表示されているページの、Beforeに位置する、つまり左側に位置するviewを呼び出す関数
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        print(viewController)

        if viewController.isKind(of : View3Controller.self) {
            // 3の時 -> 2
            return getView2()
        }

        if viewController.isKind(of : RepoViewController.self) {
            // 2の時 -> 1
            return getView1()
        }

        if viewController.isKind(of : CalendarViewController.self) {
            // 1の時 -> 3
            return getView3()
        }

        return nil
    }

    // 現在表示されているページの、Afterに位置する、つまり右側に位置するviewを呼び出す関数
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        print(viewController)

        if viewController.isKind(of : CalendarViewController.self) {
            // 1の時 -> 2
            return getView2()
        }

        if viewController.isKind(of : RepoViewController.self) {
            // 2の時 -> 3
            return getView3()
        }

        if viewController.isKind(of : UserViewController.self) {
            // 3の時 -> 1
            return getView1()
        }

        return nil
    }
}

// delegate用の関数まとめ
extension TopViewController : UIPageViewControllerDelegate{
    // スワイプによるページ遷移が行われたときに呼ばれるメソッド
    func pageViewController(_ pageViewController: UIPageViewController,
         didFinishAnimating finished: Bool,
         previousViewControllers: [UIViewController],
         transitionCompleted completed: Bool){
        
        // 現在のviewcontrollerを取得
        let currentVC = pageViewController.viewControllers?.first!

        if currentVC!.isKind(of : View1Controller.self) {
            toView1Design()
        }
        
        if currentVC!.isKind(of : View2Controller.self) {
            toView2Design()
        }
        
        if currentVC!.isKind(of : View3Controller.self) {
            toView3Design()
        }
    }
    
}

```

## 参考サイト

二つの画面をpageViewControllerで繋ぎ、スワイプでページ遷移
>[【決定版】UIPageViewControllerの使い方（Swift）](https://qiita.com/Takeshi_Akutsu/items/dbf54df8e8a50e8ed5be)

タブを作成して、タブ操作とページ遷移を紐付ける
>[UICollectionViewControllerとUIPageViewControllerでSmartNewsっぽいあのUIをお手軽に実現する](https://qiita.com/kazuhiro4949/items/ccac697ee734843f7512)