前提
- 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初期化からページング完了までを図解
これはPageViewController.viewDidLoad()
が完了した直後のイメージ図です。
薄緑色は上記のViewController
、外側の枠はPageViewController
です。
ページングを開始しますが、表示するViewControllerが存在しないため、pageViewController(_: viewControllerAfter:)
を呼び出し、先のViewControllerを生成します。
また、Before方向のViewControllerも存在していないためにpageViewController(_: viewControllerBefore:)
も呼び出し、後方のViewControllerも生成します。
ページングが完了しました。次のページング処理に備え、pageViewController(_: viewControllerAfter:)
を呼び出し、先のViewControllerを生成します。
問題のコードでは何が起きていたか
問題のコードをより反映した図になります。丸い要素はPageViewController.number
を表したものです。
After方向のスワイプイベントが発生して先のViewControllerが生成されました。
pageViewController(_: viewControllerAfter:)
によってPageViewController.number
がカウントアップされます。
また、後方のViewControllerも生成されました。
pageViewController(_: viewControllerBefore:)
によってPageViewController.number
がカウントダウンされます。
ページングが完了しました。次のページング処理に備え、先のViewControllerを生成します。
pageViewController(_: viewControllerAfter:)
によってPageViewController.number
がカウントアップされます。
まとめ
-
pageViewController(_: viewControllerBefore:)
およびpageViewController(_: viewControllerAfter:)
では、生成されようとしているViewControllerの前後のViewControllerが取得できる - なので、前後のViewControllerに対してどういう値が適切なのかを考えるようにする