コンテナview controllerを利用して、viewの一部にページコントロールを埋め込む


iOSアプリのviewの一部分に、ページコントロールで切り替えられるviewを埋め込む実装をしたので、メモ。

完成のイメージはこんな感じ。

![page_control_container.gif](https://qiita-image-store.s3.amazonaws.com/0/1795/d6a16f64-e163-8c82-6235-bbc8100c7b90.gif "page_control_container.gif")

## 動作環境
- Xcode 7.3
- iOS 9.3

## 実装
### 親view
まず親viewから。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let label = UILabel()
        label.frame.size = CGSize(width: 200, height: 100)
        label.center = view.center
        label.text = "ベースのview"
        label.textAlignment = .Center
        view.addSubview(label)
        
        // コンテナに追加
        let pageVC = PageViewController(transitionStyle: .Scroll,
                                        navigationOrientation: .Horizontal,
                                        options: nil)
        let pageViewHeight: CGFloat = 100
        pageVC.view.frame = CGRect(x: 0, y: view.bounds.size.height - pageViewHeight, width: view.bounds.size.width, height: pageViewHeight)
        
        addChildViewController(pageVC)
        view.addSubview(pageVC.view)
        pageVC.didMoveToParentViewController(self)
    }

}
```

`PageViewController`(後述) のインスタンスを、子viewのview controllerとして利用。

- `addChildViewController(_:)`で、子のview controllerとして上記の`PageViewController`を追加
- 子のviewを自身のviewに追加
- `didMoveToParentViewController(_:)`を明示的に呼び出してview controllerの追加を終了する

### 子view
親viewの中に埋め込むview controllerを作成。

複数のviewを含むページコントロールとして実装するので、`UIPageViewController`を継承させる。同時に`UIPageViewControllerDataSource`, `UIPageViewControllerDelegate`を適用。

```swift:PageViewController.swift
import UIKit

class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    
    var contentVCs = [UIViewController]()
    var currentIndex: Int = 0
    
    // MARK: - View lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor.blackColor()
        dataSource = self
        delegate = self
        
        for i in 0..<3 {
            let contentVC = PageContentViewController()
            contentVC.contentNumber = i
            contentVCs.append(contentVC)
        }
        setViewControllers([contentVCs[0]], direction: .Forward, animated: true, completion: nil)
    }
    
    // MARK: - UIPageViewControllerDataSource
    
    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
        guard let index = contentVCs.indexOf(viewController) where index != NSNotFound else { return nil }
        if index < contentVCs.count - 1 {
            return contentVCs[index + 1]
        } else {
            return contentVCs.first
        }
    }
    
    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
        guard let index = contentVCs.indexOf(viewController) where index != NSNotFound else { return nil }
        if index > 0 {
            return contentVCs[index - 1]
        } else {
            return contentVCs.last
        }
    }
    
    func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
        return contentVCs.count
    }
    
    func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
        return currentIndex
    }
    
    // MARK: - UIPageViewControllerDelegate
    
    func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        guard finished else { return }
        guard let contentVC = pageViewController.viewControllers?.first else { return }
        guard let index = contentVCs.indexOf(contentVC) where index != NSNotFound else { return }
        
        currentIndex = index
    }

}
```

- `viewDidLoad()`で、ページコントロールの中身となるview controllerを3つセット。
	- `setViewControllers(_:direction:animated:completion:)`を利用
- `pageViewController(_:viewControllerBeforeViewController:)`, `pageViewController(_:viewControllerAfterViewController:)`で、表示中の前後のview controllerを指定。
	- 今回は行き止まりを作らず、無限にスクロールさせるようになっている
- `presentationCountForPageViewController(_:)`で、ページインジケータの数をセット
	- 3個
- `presentationIndexForPageViewController(_:)`でページインジケータのインデックス（位置）をセット
	- ページインジケータの現在のインデックス (`currentIndex`) を返す
- `pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)`で、ページのtransitionが終了した時の処理を記述
	- `currentIndex`を更新

---

ページコントロールのコンテンツ部分のview controllerは以下のように実装。

`contentNumber`という変数を生成時に受け取り、それをページ番号として表示している。

```swift:PageContentViewController.swift
class PageContentViewController: UIViewController {
    
    var contentNumber: Int?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let contentNumber = contentNumber else { return }
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: 100))
        label.text = "\(contentNumber + 1)枚目"
        label.textColor = UIColor.whiteColor()
        label.textAlignment = .Center
        view.addSubview(label)
    }
}
```

## おわり
以上で実装完了。

もう少し複雑ですが、[yenta](https://yenta.talentbase.io/yenta/)というiOSアプリで、こんな感じの実装をしました。

## 参考
- [Container View Controllerを正しく使おう](http://www.slideshare.net/asakahara/container-viewcontroller)