UIPageViewController 八つの掟
結構理解しているつもりだった UIPageViewController でしたが、毎回同じようなところで何度もハマるので、忘れる前に記憶の整理を兼ねてメモしておく事とします。
一、PageCurl と Scroll を動的に変更したい場合は、InterfaceBuilderではなく、コードで作成すべし
transitionStyle は readonly なので、InterfaceBuilder でインスタンス化させると、transitionStyle は変更できません。それで PageCurl と Scroll を動的に変更したい場合は、自分の ViewController 内に、コードでインスタンス化させて追加します。そうすれば、変更したい場合は、UIPageViewController 自体を一旦削除して作り直す事ができます。
var transitionStyle: UIPageViewControllerTransitionStyle { get }
transitionStyleProperty
The style used to transition between view controllers. (read-only)
(追記)self.addChildViewController(pageViewController) も忘れないでください。
pageViewController.willMoveToParentViewController(self)
self.addChildViewController(pageViewController)
pageViewController.setViewControllers(...)
pageViewController.view.frame = self.view.bounds
self.view.addSubview(pageViewController.view)
pageViewController.didMoveToParentViewController(self)
一、PageCurl で見開き対応したい時 UIPageController の viewControllers と spineLocation を気をつけるべし
UIPageViewController の transitionStyle を PageCurl にした時は、UIPageController の delegate が呼ばれて、pageViewController:spineLocationForInterfaceOrientation: が呼ばれます。spineLocation は .None, .Min, .Mid, .Max とありますが、その組み合わせは以下の通りです。
(Landscapeの場合)
| ページ | 右左綴じ | Spine | PageCurl | Scroll |
|---|---|---|---|---|
| 単ページ表示 | 左綴じ(横書き) | .Min | 1 | 1 |
| 単ページ表示 | 右綴じ(縦書き) | .Max | 1 | 1 |
| 見開き表示 | どちらでも | .Mid | 2 | 1 |
Scroll モード見開き表示の場合は、実質2ページであるにもかかわらず、viewControllers には1つの viewController しか配置できません。そこで、見開き用の viewController を用意して、その中に2つの viewController を配置する必要があります。
+ UIViewController (見開き用)
- UIViewController (ページ用)
- UIViewController (ページ用)
一、PageCurlモードで、単ページ表示および見開きページの場合、右綴じは順序を逆にすべし
書籍などのコンテンツで、全ページ分 ViewController を準備する場合、右綴じの場合はその順序を逆に用意する必要があります。
[1] [2] [3] [4] [5] [6] (左綴じ)
[6] [5] [4] [3] [2] [1] (右綴じ)
[1|2] [3|4] [5|6] (左綴じ)
[6|5] [4|3] [2|1] (右綴じ)
もしくは、pageViewController:viewControllerBeforeViewController: または pageViewController:viewControllerAfterViewController: のdelegate で右綴じ左綴じに応じて順方法または逆方法に viewController にアクセスしてやる必要があります。
pageViewController:viewControllerAfterViewController:
一、PageCurlモードで、見開きページの場合の場合、最初と最後のページも viewController を2つ用意するべし
多くの書籍は表紙、裏表紙を含めて偶数ページですが、その場合、表紙 ViewController と対となるダミー ViewController を用意しなくてはならなりません。
[x|1] [2|3] [4|5] [6|x] (x はダミーページ)
一、単ページ表示の時、ダミーページを表示させるべからず
先のダミーページを含む見開き表示の場合、デバイスを回転させて単ページ表示になった時にダミーページを表示させてはいけません。わざとそうしたい場合はその限りではありません。
(横) (縦)
[x|1] -> [1]
[6|x] -> [6]
一、PageCurl モードは pageViewController:spineLocationForInterfaceOrientation: で調整すべし
iOS8 より interfaceOrientation が Deprecated になりました。Landscape と Portrait の判定には、実際の view の縦と横のサイズを使うなど、別の方法が要求されるようになりました。しかし、この方法を使うと落とし穴もあります。pageViewController:spineLocationForInterfaceOrientation: が呼ばれた時にまだ、view.bounds が反映されていない時があるからです。
interfaceOrientation が Deprecated なのに、pageViewController:spineLocationForInterfaceOrientation: が interfaceOrientation を戻してくるのはいい度胸をしています。とにかく、pageViewController:spineLocationForInterfaceOrientation: が呼ばれた時、先ほどの表を元に、正しい spineLocation と 正しい数と内容の viewController をセットしましょう。
一、Scroll モードは viewWillLayoutSubviews で、UIPageViewControllerの調整をすべし
Scroll モードの場合は、pageViewController:spineLocationForInterfaceOrientation: は呼ばれません、pageViewController:spineLocationForInterfaceOrientation: 内で全て調整していると、Scroll モードの時に、デバイスを回転させても、UIPageViewController は素知らぬ顔です。
Scroll モードの場合は、viewWillLayoutSubviews などで UIPageViewController の view のサイズを確認して、Landscape または Portrait の判定を行い、単ページ・複数ページ表示の切り替えを行います。
そんなの PageCurlモードもこちらで調整すればいいと思うかもしれませんが、デバイスを回転させた時はpageViewController:spineLocationForInterfaceOrientation: が呼ばれ、正しい解答を答えないと例外が発生されてしまいます。spineLocation が readonly なので、この時に今後の予定の状態を正しくセットしておかないと、後で設定はできないのです。
一、Scroll モード、見開き、右綴じの場合、ページの位置を逆にすべし
Scroll モードの時の見開きページの場合に、見開き用ViewControllerを使う場合に、右綴じの場合、ページの配置を
[x|6] [5|4] [3|2] [1|x] -> OK
[6|x] [4|5] [2|3] [ |1] -> NG
おまけ、interfaceOrientation に頼らず、縦横を判定する方法
1. ViewController に オプショナルな CGSize? を用意する
var viewTransitioningSize: CGSize?
2. viewWillTransitionToSize: で直近で設定されるサイズを保持
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
self.viewTransitioningSize = size // <-- before super
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
// your code
}
3. viewWillLayoutSubviews で保持しているサイズを廃棄
override func viewWillLayoutSubviews() {
self.viewTransitioningSize = nil
super.viewWillLayoutSubviews()
}
4. Landscape Portrait を判定するメソッドを用意する
var isPortrait: Bool {
assert(self.isViewLoaded())
let viewSize = viewTransitioningSize ?? self.view.bounds.size
return viewSize.height > viewSize.width
}
これで、pageViewController:spineLocationForInterfaceOrientation: が呼ばれている最中で、view.bounds が確定していない時でも、Landscape と Portrait の判定が可能になります。
(参考資料)
UIPageViewController class reference - Apple Developer
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPageViewControllerClassReferenceClassRef/
