Help us understand the problem. What is going on with this article?

UIPageViewControllerでページ遷移時に処理したいことを書くときのTip

たまに解説サイトにあるアンチパターン

ページを変わったときにする処理をUIPageViewControllerDataSourcepageViewController関数中に書く解説サイトをいくつか見ます。

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のときじゃないと、画面遷移を失敗したときにも呼ばれるっぽいです。

PageViewController.swift
// Delegate内
if completed {
    callWhenPageChanged()
}

もし画面遷移が行われないときにcallWhenPageChangedが呼ばれるとまずいケースもあると思うので、completedで分岐させるのは必要ですね。

最後に

UIPageViewControllerDelegateの使い方をちょっとだけ解説しました。間違っていることがあればご報告お願いします。

freddi_
プログラミングを捨てて、悟りを開くのが夢です
https://freddi.dev
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away