【iOS】UIPageViewControllerでのキャッシュ機能への対応


**UIPageViewController**を使って、複数のUIViewControllerをページングするように実装していたところ、ページ切り替えがうまくできなかったのでそれの対処方法の紹介です。

ちなみにですが、この現象 **iOS8**では発生しませんでした。（iOS シミュレーター調べ）

## 対応策

先に結論を述べますと、ページ遷移がうまくいきませんでした。

なのでページを自前で切り替える時に以下のようにしたらうまくいきました。

```swift
// どっちにアニメーションするか
let direction = .Forward
// 適当なViewController
let viewController = viewControllers[pageIndex]

pageViewController.setViewControllers([viewController], direction: direction, animated: true, completion: { [weak self] finished in
	if !finished {
		return
	}
	dispatch_async(dispatch_get_main_queue()) {
		self?.pageViewControllers([viewController], direction: direction, animated: true, completion:nil)
	}
})
```

という感じでできました。

以下は原因とかやりたかったことを詳しく書いたので、よかったら読んでみてください。

## 具体例

**UIPageViewController**に`delegate`と`dataSource`をつけて、３ページをスクロールするようにしました。

以下のように実装したとします。

```swift:ViewController.swift
class ViewController: UIViewController {
	
	// 表示するViewController
	lazy var viewControllers: [SubViewController] = {
		var temporaryViewControllers = [SubViewController]
		for var i = 0; i < 3; i++ {
			let viewController = SubViewController.instantiateViewController(text: "Page \(i)")
			temporaryViewControllers.append(viewController) 		}
		return temporaryViewControllers
	}()
	
	lazy var pageViewController: UIPageViewController = {
		let pageViewController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, option: nil)
		pageViewController.delegate = self
		pageViewController.dataSource = self
	}
	
	private var currentPageIndex: Int = 0
	
	override func viewDidLoad() {
		setupPageViewController()
	}
}

// MARK: - UIPageViewController dataSource

extension ViewController: UIPageViewControllerDataSource {
	
	func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController: viewController: UIViewController) -> UIViewController? {
		// 今表示しているViewControllerの一つ前にViewControlelrを返す
		// なければnilで返す
	}
	
	func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController: viewController: UIViewController) -> UIViewController? {
		// 今表示しているViewControllerの一つ後にViewControlelrを返す
		// なければnilで返す
	}
}

// MARK: - UIPageViewController delegate

extension ViewController: UIPageViewControllerDelegate {
	
	// ViewControllerのアニメーションなりが終わった段階で呼ばれるメソッド
	func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) {
		if let viewController = pageViewController.viewControllers.first as? SubViewController {
			// Arrayにfindというメソッドはありません。
			// extensionで自前で拡張しました。
			if let index = viewControllers.find({ $0 == viewController }) {
				currentPageIndex = index
			}
		}
	}

}



// MARK: - setup

extension ViewController {
	
	private func setupPageViewController() {
		let firstViewController = viewControllers.first!
		pageViewController.setViewControllers([firstViewController], direction: .Forward, animated: false, completion: nil)
		pageViewController.view.frame = view.bounds
		pageViewController.view.autoresizingMask = (.FlexibleHeight | .FlexibleWidth)
		addChildViewController(pageViewController)
		view.addSubview(pageViewController.view)
		pageViewController.didMoveToParentViewController(self)
	}
}
```

```swift:SubViewController.swift
class SubViewController: UIViewController {
	
	class func instantiateViewController(#text: String) -> SubViewController {
		let storyboard = UIStoryboard(name: "Main", bundle: nil)
		let viewController = storyboard.instantiateViewControllerWithIdentifier("SubViewController") as! SubViewController
		viewController.text = text
		return viewController
	}
	
	@IBOutlet weak var textLabel: UILabel!
	
	var text: String!
	
	override func viewDidLoad() {
		super.viewDidLoad()
		textLabel.text = text
	}
	
}
```

というクラスを用意しました。

（細かい処理は省かせていただきました...。）

## 今回の現象

上記の例をそのまま使います。

現在 **1ページ**目にいたとしてそこから **3ページ**目に一気に飛びたい時は以下のようにします。

```swift:ViewController.swift
// MARK : jump page

extension ViewController {

	func jump(#pageIndex: Int) {
		let viewController = viewControllers[pageIndex]
		let direction = (currentPageIndex < pageIndex) ? .Forward : .Reverse
		pageViewController.setViewControllers([viewController], direction: direction, animated: true, completion: nil)
	}
}
```

こうすれば指定のページへスクロールすることができます。

しかしこの後横スクロールで違う画面へ行くと、 **１つ前に表示していた（１ページ目）画面しか表示されません。**

## 条件

以下の条件に当てはまると同じ現象が起きるかと思います。

- **UIPageViewController**の`transitionStyle`に`.Scroll`を指定している場合
- **UIPageViewController**の`setPageViewControllers(_:direction:animated:completion)`で`animated`に`true`を渡している場合

上記を満たしていると **UIPageViewController**がキャッシュをして他のページを読み込まない事態が発生します。

それを解消するために対応策を講じると、うまくいったという感じです。

## 最後に

これとっても困りました。

いろいろ調べて解決できて嬉しかったので、書きました。

以上になります。

## 参考

[[Objective-C] UIPageViewControllerのキャッシュが邪魔をする件 | 杏z 学習帳](http://anz-note.tumblr.com/post/107596874066/objective-c-uipageviewcontroller)

本当にお世話になりました！