post Image
pageViewController(_: viewControllerBefore:) および pageViewController(_: viewControllerAfter:) が呼び出されるタイミングについて(UIPageViewController)

前提

  • Swift 3
  • 表示するViewControllerは常時1枚だけ

経緯

  • publicなプロパティにセットされた数字を表示するだけのViewControllerがある
  • UIPageViewControllerを使い、端の制限がないページングを行いたかった
    • スワイプすることで上記ViewControllerの数字をカウントアップ、カウントダウンさせたかった

tl;dr

  • PageViewControllreは表示中のViewControllerの前後になるべくViewControllerを生成して待機させている
  • 初期化してから初めてのページング開始時にpageViewController(_: viewControllerBefore:)およびpageViewController(_: viewControllerAfter:)が一度ずつ呼ばれる
  • ページング完了時に、ページング方向応じてpageViewController(_: viewControllerBefore:)およびpageViewController(_: viewControllerAfter:)が一度呼ばれる
  • 引数のviewControllerを元に相対で適切な値を決定してあげましょう

期待したとおりに動かないコード

class ViewController: UIViewController {
    public var number: Int = 0
    @IBOutlet weak var simpleLabel: UILabel!

    override func viewWillAppear(_ animated: Bool) {
        simpleLabel.text = number.description
    }
}
class PageViewController: UIPageViewController, UIPageViewControllerDataSource {
    var number: Int = 0
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = self

        // 初期表示するViewControllerをセットする
        let vc:ViewController = // コード生成したりStoryboardから取ったり
        vc.number = self.number
        self.setViewControllers([vc], direction: .forward, animated: true)
    }
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        // 後方のViewControllerを生成するため、カウントダウンしたい
        let vc:ViewController = // コード生成したりStoryboardから取ったり
        self.number -=1
        vc.number = self.number
        return vc
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        // 前方のViewControllerを生成するため、カウントアップしたい
        let vc:ViewController = // コード生成したりStoryboardから取ったり
        self.number +=1
        vc.number = self.number
        return vc
    }

}

期待したとおりに動くコード

class ViewController: UIViewController {
    public var number: Int = 0
    @IBOutlet weak var simpleLabel: UILabel!

    override func viewWillAppear(_ animated: Bool) {
        simpleLabel.text = number.description
    }
}
class PageViewController: UIPageViewController, UIPageViewControllerDataSource {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = self

        // 初期表示するViewControllerをセットする
        let vc:ViewController = // コード生成したりStoryboardから取ったり
        vc.number = 0
        self.setViewControllers([vc], direction: .forward, animated: true)
    }
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        // 後方のViewControllerを生成するため、カウントダウンしたい
        let vc:ViewController = // コード生成したりStoryboardから取ったり
        vc.number = (viewController as! ViewController).number - 1
        return vc
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        // 前方のViewControllerを生成するため、カウントアップしたい
        let vc:ViewController = // コード生成したりStoryboardから取ったり
        vc.number = (viewController as! ViewController).number + 1
        return vc
    }

}

該当メソッドが呼び出されるタイミングについて整理

PageViewController初期化からページング完了までを図解

pagevc-1.jpg
これはPageViewController.viewDidLoad()が完了した直後のイメージ図です。
薄緑色は上記のViewController、外側の枠はPageViewControllerです。

pagevc-1 (1).jpg
After方向のスワイプイベントが発生しました。

pagevc-1 (2).jpg
ページングを開始しますが、表示するViewControllerが存在しないため、pageViewController(_: viewControllerAfter:)を呼び出し、先のViewControllerを生成します。

pagevc-1 (3).jpg
また、Before方向のViewControllerも存在していないためにpageViewController(_: viewControllerBefore:)も呼び出し、後方のViewControllerも生成します。

pagevc-1 (4).jpg
ページング中……

pagevc-1 (5).jpg
ページングが完了しました。次のページング処理に備え、pageViewController(_: viewControllerAfter:)を呼び出し、先のViewControllerを生成します。

問題のコードでは何が起きていたか

pagevc-1 (6).jpg
問題のコードをより反映した図になります。丸い要素はPageViewController.numberを表したものです。

pagevc-1 (7).jpg
After方向のスワイプイベントが発生して先のViewControllerが生成されました。
pageViewController(_: viewControllerAfter:)によってPageViewController.numberがカウントアップされます。

pagevc-1 (8).jpg
また、後方のViewControllerも生成されました。
pageViewController(_: viewControllerBefore:)によってPageViewController.numberがカウントダウンされます。

pagevc-1 (9).jpg
ページング中……

pagevc-1 (10).jpg
ページングが完了しました。次のページング処理に備え、先のViewControllerを生成します。
pageViewController(_: viewControllerAfter:)によってPageViewController.numberがカウントアップされます。

まとめ

  • pageViewController(_: viewControllerBefore:)およびpageViewController(_: viewControllerAfter:)では、生成されようとしているViewControllerの前後のViewControllerが取得できる
  • なので、前後のViewControllerに対してどういう値が適切なのかを考えるようにする

『 Swift 』Article List