たまに解説サイトにあるアンチパターン
ページを変わったときにする処理をUIPageViewControllerDataSource
のpageViewController
関数中に書く解説サイトをいくつか見ます。
extension PageViewController : UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
...
callWhenPageChanged() // ページが変わったら呼びたい処理
... /*何かしらのViewControllerをreturnする処理*/
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
...
callWhenPageChanged() // ページが変わったら呼びたい処理
... /*何かしらのViewControllerをreturnする処理*/
}
}
callWhenPageChanged()
は、例えば、UIPageControl
(インジケータ)の現在ページの操作を変更するための処理などが考えられますね。その場合、遷移先になりうるViewControllerの情報が必要になるのも理解できます。
確かにここのpageViewController
は、左右にフリックしたときなどに、ページデータを提供するために呼ばれるのは確かです。
しかし、あくまでページリソースを扱う前提の関数に、そのような処理を書くのは本質的ではないと私は思います。
というか、本質的でないというか、この実装は間違えています。 この2つの関数は現在のページの前後のページにあたるViewControllerを返す関数です。ページが変わるごとに毎回呼ばれるので、毎ページでcallWhenPageChanged
が2回呼ばれますね。
で、何を使えばいいの?
実は、すでにUIPageViewControllerDelegate
という素晴らしいDelegateが存在します。
リファレンスにもある通り、UIPageViewControllerDelegate
は、以下2つの実装をサポートしています。
// ジェスチャーによる遷移が始まる場合に呼ばれる
// willTransitionTo.firstを見れば、次の遷移先のページのインスタンスを見ることができる
func pageViewController(UIPageViewController, willTransitionTo: [UIViewController])
// ジェスチャーによる遷移が終わった場合に呼ばれる
func pageViewController(UIPageViewController, didFinishAnimating: Bool,
previousViewControllers: [UIViewController],
transitionCompleted: Bool)
これらは、ジェスチャーによる画面遷移イベントが発生した呼ばれる関数です。こちらのどちらかに書くほうが適してますね。
現在のページ(ViewController)を取得したい
具体的に、callWhenPageChanged
を使いたいときの例を書いてみます。
extension PageViewController: UIPageViewControllerDelegate {
// ジェスチャーによる遷移が終わった場合に呼ばれる
func pageViewController(_ viewController: UIPageViewController, didFinishAnimating: Bool,
previousViewControllers: [UIViewController],
transitionCompleted: Bool) {
...
callWhenPageChanged() // ページが変わったら呼びたい処理
...
}
}
では、現在表示されているページ(ViewController)を取得を取得したい場合はどうすればいよいでしょうか?
上記の関数(didFinishAnimating
がある方)では、第1引数のviewController.viewControllers.first
が現在表示されているページに当たりますので、それを見てもらえばよいです。
追記
以下 @azuma317 さんからコメントを頂いたのでご紹介します。
DataSourceなので、ページ構成時に呼ばれそうだけど、意外と呼ばれなかったりしますね...
遷移後の処理はDelegateに書くべきだけど、transitionCompletedがtrueのときじゃないと、画面遷移を失敗したときにも呼ばれるっぽいです。
// Delegate内
if completed {
callWhenPageChanged()
}
もし画面遷移が行われないときにcallWhenPageChanged
が呼ばれるとまずいケースもあると思うので、completed
で分岐させるのは必要ですね。
最後に
UIPageViewControllerDelegate
の使い方をちょっとだけ解説しました。間違っていることがあればご報告お願いします。