UIPageViewController 八つの掟
結構理解しているつもりだった UIPageViewController
でしたが、毎回同じようなところで何度もハマるので、忘れる前に記憶の整理を兼ねてメモしておく事とします。
一、PageCurl と Scroll を動的に変更したい場合は、InterfaceBuilderではなく、コードで作成すべし
transitionStyle
は readonly なので、InterfaceBuilder でインスタンス化させると、transitionStyle
は変更できません。それで PageCurl
と Scroll
を動的に変更したい場合は、自分の ViewController 内に、コードでインスタンス化させて追加します。そうすれば、変更したい場合は、UIPageViewController
自体を一旦削除して作り直す事ができます。
var transitionStyle: UIPageViewControllerTransitionStyle { get }
transitionStyle
Property
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/