UIPageViewControllerで本アプリを作る。[Swift4]

# はじめに

* UIPageViewController を使って、本のようなアプリを作成します。
* 各ページは、ピンチ操作で拡大ができるようにします。
* 私も Swift 初心者なので、自身の学習を目的とした投稿であり、効率的な構成ではないですが、ご参考いただければ幸いです。

## 対象者

* Swift超初心者

## ゴールイメージ

![Bitmap.png](https://qiita-image-store.s3.amazonaws.com/0/10317/8b8f4bce-b484-f3d7-8485-6b5791c1b098.png)



# Step0. プロジェクトの作成

## 概要
* SingleViewApps としてプロジェクトを作成し、横配置専用とするため、Portrait表示をオフにします。

## 手順
<img width="1512" alt="スクリーンショット 2017-11-23 13.50.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/e3342a01-7880-a252-91c0-5ab161ff424b.png">

Product Name は、「BookAppli」
<img width="1512" alt="スクリーンショット 2017-11-23 13.52.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/069e8ef6-48ed-11e7-4c7e-b05f2522375e.png">

Device Orientation の設定で、Portraitのチェックを外す
![スクリーンショットportrait.png](https://qiita-image-store.s3.amazonaws.com/0/10317/1a8c9ccf-ebe6-8b24-b790-0d030e29c8c9.png)


# Step1. Storyboardの配置
## 概要
* 4ページの構成とするため、ViewControllerを4つ配置します。
* ピンチ操作でページの拡大を可能にするため、各ViewController上に、「UIScrollView」を配置します。
* 各ページの内容は、PNGファイルで作成したものを利用するため、UIImageView を UIScrollViewの上に配置します。
* 画像は、ページいっぱいに広がるように調整します。

## 手順1-1. Viewの配置
横配置のアプリなので、View エリアの設定で、表示形式を横配置に
![スクリーンショット 2017-11-23 13.54.43.png](https://qiita-image-store.s3.amazonaws.com/0/10317/176352d0-a6f7-7016-a549-e098f89a40b5.png)

ViewController を3個ドラッグ＆ドロップして、適当に並べる
![スクリーンショット 2017-11-23 13.55.48.png](https://qiita-image-store.s3.amazonaws.com/0/10317/02ea60d4-335f-c9c6-c455-c0814d3db965.png)

ViewController の上に、ScrollView をドラッグ＆ドロップ
![スクリーンショット 2017-11-23 14.07.25.png](https://qiita-image-store.s3.amazonaws.com/0/10317/e156f73e-f70e-6c91-970b-f4d9eec5ad43.png)

画面いっぱいにするため、Constraints で四方との距離ゼロに
<img width="1512" alt="スクリーンショット 2017-11-23 14.08.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/749157a4-90c4-09e7-4ea3-ddb2c2b3ccbf.png">

適用後
<img width="1512" alt="スクリーンショット 2017-11-23 14.08.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/9bfdd4ce-6f13-0b16-655f-0fe01dc61ef1.png">

残りの ViewController にも ScrollView を配置。
<img width="1512" alt="スクリーンショット 2017-11-23 14.09.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/04a76fa6-fc27-fd49-78d3-f42552209190.png">

次に、ScrollView 上に ImageView を配置。
![スクリーンショット 2017-11-23 14.09.57.png](https://qiita-image-store.s3.amazonaws.com/0/10317/c95590b5-4189-84e9-5431-0a712c5c7d9c.png)

上下左右の Constraints をゼロに設定し、中央に配置し、
![スクリーンショット 2017-11-23 14.10.14.png](https://qiita-image-store.s3.amazonaws.com/0/10317/ee89f023-dea3-df4d-5b01-419391f93f0b.png)

画面いっぱいにするため、 四方との Constraints もゼロに設定。
![スクリーンショット 2017-11-23 14.10.37.png](https://qiita-image-store.s3.amazonaws.com/0/10317/53cd4b33-5cb4-0e96-6d48-366b132ff552.png)

設定後
<img width="1512" alt="スクリーンショット 2017-11-23 14.10.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/f28a2b5e-7f65-0cd8-48c2-14d50ac2f231.png">

残りの3つも、同様に。
<img width="1512" alt="スクリーンショット 2017-11-23 14.12.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/2ed199ea-14f4-b11b-a322-7490291964e0.png">

## 手順1-2. 画像ファイルの配置

* サンプルとして、「ページ1」〜「ページ4」と書いただけの PNG 画像ファイルを準備した。

Assets.xcassets を選択し、画像ファイルをドラッグ＆ドロップする
![スクリーンショット 2017-11-23 14.19.31.png](https://qiita-image-store.s3.amazonaws.com/0/10317/788e072d-feb7-3b2d-c6c2-ce18dddf022e.png)

Storyboard に戻り、各 ImageView の画像をセットする
![スクリーンショット 2017-11-23 14.20.34.png](https://qiita-image-store.s3.amazonaws.com/0/10317/5fabe658-8d5a-74bb-fb20-22ddb192457b.png)

残りも同様に。
<img width="1512" alt="スクリーンショット 2017-11-23 14.20.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/a355875f-3c46-4627-6f68-c75226b1d012.png">

とりあえず、ここまででビルド実行してみる。処理は実装していないので、当然ながら1ページ目が表示されるのみ。
<img width="1050" alt="スクリーンショット 2017-11-23 14.22.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/400fcb4c-fa7d-eda5-e08d-ab89b27f7283.png">

## 手順1-3. 追加した3つのViewControllerをソースコードと関連づける
メニューから、[File] -> [New] -> [File] を選択し、[Cocoa Touch Class] を選択する。
<img width="1512" alt="スクリーンショット 2017-11-23 14.55.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/d61079fe-2843-6c62-cc62-f454f6474cd4.png">

Class名を、ViewController02 とし、Subclass of: で [UIViewController] を選択する。
<img width="730" alt="スクリーンショット 2017-11-23 23.28.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/28eb4839-d519-2730-7d54-52f541ae798e.png">

ここにファイルができあがる。
![スクリーンショット 2017-11-23 14.57.31.png](https://qiita-image-store.s3.amazonaws.com/0/10317/a6bd7c24-0af2-3eb1-dbd1-3d138051ba80.png)

同じように、ViewControlleer03, 04 も作る。
![スクリーンショット 2017-11-23 14.58.59.png](https://qiita-image-store.s3.amazonaws.com/0/10317/12ee7dab-c214-41e9-8a2d-218a4c7ebb29.png)

ファイルを、もともとあった ViewController.swift と同じ並びに移動させる。（ドラッグ＆ドロップ）
![スクリーンショット 2017-11-23 14.59.20.png](https://qiita-image-store.s3.amazonaws.com/0/10317/4c74c19c-eb57-6607-ad97-b094de4e54cb.png)

ページ番号としての Identify として、 ViewContoroller(ページ1) の Storyboard ID を [vc01] とする。
![スクリーンショット 2017-11-23 15.00.18.png](https://qiita-image-store.s3.amazonaws.com/0/10317/8fe59202-b82f-bcb9-a138-fc95b6ba319b.png)

ページ2 を、ViewController02.swift と紐付けるため、Custom Class の Class を、ViewController02 に変更する。
![スクリーンショット 2017-11-23 15.00.28.png](https://qiita-image-store.s3.amazonaws.com/0/10317/d15449a1-e347-cf69-28be-b2f4477fbdeb.png)

Storyboard ID は、[vc02] とする。
![スクリーンショット 2017-11-23 15.00.36.png](https://qiita-image-store.s3.amazonaws.com/0/10317/5304e56d-1114-fcdb-e5e8-303fa5fb3e02.png)

のこりの 03, 04 の ViewController も、ソースコード紐付けと、Storyboard ID をセットする。
<img width="1512" alt="スクリーンショット 2017-11-23 15.01.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/dd6d84a1-2567-b1a7-ae0c-559cda4e51b3.png">

以上で、Step1 は終了。

# Step2. ピンチ操作で拡大する処理を実装する
## 手順2-1. IBOutlet で、UIScrollView と、UIImageView を作成する。

画面右上の、このマークをクリックし、storyboard とソースコードを表示させる。
![スクリーンショット 2017-11-29 21.24.15.png](https://qiita-image-store.s3.amazonaws.com/0/10317/dc6951b9-0e7a-a432-52e8-b2e9d22506ab.png)

ScrollView を、右クリックでドラッグし、ソースコード上にドロップする。
![makeMyScrollView.png](https://qiita-image-store.s3.amazonaws.com/0/10317/888e021e-4b79-9ce4-77ae-55120de34500.png)

名称は、myScrollView としておく。
![スクリーンショット 2017-11-29 21.03.01.png](https://qiita-image-store.s3.amazonaws.com/0/10317/8321c54e-afca-2878-3136-e34ecd4588e4.png)

Connect をクリックすると、こうなる。ソースコード上に、IBOutlet が追加されている。
<img width="1512" alt="スクリーンショット 2017-11-29 21.07.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/2ea32c7e-9842-b174-1451-d64ca6fe8b06.png">

同様に、UIImageView （表示上は、「page1」となっている）も IBOutlet と紐付ける。
名称は、myImageView としておく。
<img width="1512" alt="スクリーンショット 2017-11-29 21.08.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/3c410f13-4d92-05e8-5867-cdb44d85e231.png">

IBOutlet の作成完了。
<img width="1512" alt="スクリーンショット 2017-11-29 21.09.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/3e500df1-5485-3f51-7958-851672e29a98.png">

同様に、ViewController2, 3, 4 へも IBOutlet を作成しておく。
名称は、ViewController と同じく、 myScrollView, myImageView でOK。
<img width="1512" alt="スクリーンショット 2017-11-29 21.43.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/6a7e6971-7bfb-4113-70e1-4c934d782010.png">


## 手順2-2. ピンチ操作で拡大するコードを書く

まず、3行目。UIScrollViewDelegate を継承させる。
続いて、viewDidLoad() 関数内に、初期化処理を記載。
最後に、viewForZooming() 関数を作成する。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController, UIScrollViewDelegate {


    @IBOutlet weak var myScrollView: UIScrollView!
    @IBOutlet weak var myImageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        self.myScrollView.delegate = self
        
        self.myScrollView.minimumZoomScale = 1
        self.myScrollView.maximumZoomScale = 4
        self.myScrollView.isScrollEnabled = true
        self.myScrollView.showsHorizontalScrollIndicator = true
        self.myScrollView.showsVerticalScrollIndicator = true
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return self.myImageView
    }

}

```

ここまでで、とりあえずビルドして実行してみる。
Option キーを押しながらドラッグすることで、ピンチ操作ができる。
拡大表示されれば成功。
<img width="1050" alt="スクリーンショット 2017-11-29 21.54.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/b4d7d633-cd43-6578-0ebb-461be7963707.png">


ViewController2.swift も同様に実装する。
差分は、class 名だけとなる。

```swift:ViewController2.swift
import UIKit

class ViewController02: UIViewController, UIScrollViewDelegate {
    
    
    @IBOutlet weak var myScrollView: UIScrollView!
    @IBOutlet weak var myImageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        self.myScrollView.delegate = self
        
        self.myScrollView.minimumZoomScale = 1
        self.myScrollView.maximumZoomScale = 4
        self.myScrollView.isScrollEnabled = true
        self.myScrollView.showsHorizontalScrollIndicator = true
        self.myScrollView.showsVerticalScrollIndicator = true
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return self.myImageView
    }
    
}
```

ViewController3, 4 も同様にやっておくこと。

# Step3. UIPageViewController を実装する
## 手順3-1. Storyboard に PageViewControllerを作成
PageViewController を Storyboard にドラッグ&ドロップし、「Is Initial View Controller」にチェックを入れる
![スクリーンショット 2017-11-29 22.09.03.png](https://qiita-image-store.s3.amazonaws.com/0/10317/51d167fe-4b70-d92d-6201-f31ece65d2ba.png)

新規ファイル作成で、UIPageViewController のサブクラスを作成する。
ファイル名は、PageViewController.swift とする。
<img width="1512" alt="スクリーンショット 2017-11-29 22.10.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/10317/7265db8a-f071-a9ed-fe52-016109ba9dcc.png">

Storyboard との紐付けも忘れずに。
![スクリーンショット 2017-11-29 22.10.52.png](https://qiita-image-store.s3.amazonaws.com/0/10317/8d102400-27d9-67e6-d253-4266cf35f94a.png)


## 手順3-2. ViewController を格納する構造体を作成する

```swift
struct PageSettings {
    static let pageControllerIdentifierList : [String] = [
        "vc01",
        "vc02",
        "vc03",
        "vc04"
    ]
    
    static func generateViewControllerList() -> [UIViewController] {
        var viewControllers : [UIViewController] = []
        self.pageControllerIdentifierList.forEach{ viewControllerName in
            let viewController = UIStoryboard(name: "Main", bundle: nil) . instantiateViewController(withIdentifier: "\(viewControllerName)")
            
            viewControllers.append(viewController)
        }
        return viewControllers
    }
}
```

## 手順3-3. 初期化処理

```swift
class PageViewController: UIPageViewController {
    
    var viewControllerIndex : Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.setViewControllers([PageSettings.generateViewControllerList().first!], direction: .forward, animated: true, completion: nil)
        self.dataSource = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
```

## 手順3-4. ページ送りの部分を実装。

```swift
extension PageViewController : UIPageViewControllerDataSource {
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        
        let targetViewControllers : [UIViewController] = PageSettings.generateViewControllerList()
        
        if viewControllerIndex == targetViewControllers.count - 1 {
            return nil
        } else {
            viewControllerIndex = viewControllerIndex + 1
        }
        
        return targetViewControllers[viewControllerIndex]
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        
        let targetViewControllers : [UIViewController] = PageSettings.generateViewControllerList()
        
        if viewControllerIndex == 0 {
            return nil
        } else {
            viewControllerIndex = viewControllerIndex - 1
        }
        
        return targetViewControllers[viewControllerIndex]
    }

}
```

## 参考
全部まとめて貼り付ける。

```swift:PageViewController.swift
import UIKit

struct PageSettings {
    static let pageControllerIdentifierList : [String] = [
        "vc01",
        "vc02",
        "vc03",
        "vc04"
    ]
    
    static func generateViewControllerList() -> [UIViewController] {
        var viewControllers : [UIViewController] = []
        self.pageControllerIdentifierList.forEach{ viewControllerName in
            let viewController = UIStoryboard(name: "Main", bundle: nil) . instantiateViewController(withIdentifier: "\(viewControllerName)")
            
            viewControllers.append(viewController)
        }
        return viewControllers
    }
}

class PageViewController: UIPageViewController {
    
    var viewControllerIndex : Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.setViewControllers([PageSettings.generateViewControllerList().first!], direction: .forward, animated: true, completion: nil)
        self.dataSource = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

extension PageViewController : UIPageViewControllerDataSource {
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        
        let targetViewControllers : [UIViewController] = PageSettings.generateViewControllerList()
        
        if viewControllerIndex == targetViewControllers.count - 1 {
            return nil
        } else {
            viewControllerIndex = viewControllerIndex + 1
        }
        
        return targetViewControllers[viewControllerIndex]
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        
        let targetViewControllers : [UIViewController] = PageSettings.generateViewControllerList()
        
        if viewControllerIndex == 0 {
            return nil
        } else {
            viewControllerIndex = viewControllerIndex - 1
        }
        
        return targetViewControllers[viewControllerIndex]
    }

}
```

以上！