LoginSignup
54
58

More than 5 years have passed since last update.

UIPageViewController 八つの掟

Last updated at Posted at 2016-01-27

UIPageViewController 八つの掟

結構理解しているつもりだった UIPageViewController でしたが、毎回同じようなところで何度もハマるので、忘れる前に記憶の整理を兼ねてメモしておく事とします。

screenshot.jpg

一、PageCurl と Scroll を動的に変更したい場合は、InterfaceBuilderではなく、コードで作成すべし

transitionStyle は readonly なので、InterfaceBuilder でインスタンス化させると、transitionStyle は変更できません。それで PageCurlScroll を動的に変更したい場合は、自分の 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 を気をつけるべし

UIPageViewControllertransitionStylePageCurl にした時は、UIPageControllerdelegate が呼ばれて、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 になりました。LandscapePortrait の判定には、実際の 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 などで UIPageViewControllerview のサイズを確認して、Landscape または Portrait の判定を行い、単ページ・複数ページ表示の切り替えを行います。

そんなの PageCurlモードもこちらで調整すればいいと思うかもしれませんが、デバイスを回転させた時はpageViewController:spineLocationForInterfaceOrientation: が呼ばれ、正しい解答を答えないと例外が発生されてしまいます。spineLocationreadonly なので、この時に今後の予定の状態を正しくセットしておかないと、後で設定はできないのです。

一、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 が確定していない時でも、LandscapePortrait の判定が可能になります。


(参考資料)
UIPageViewController class reference - Apple Developer
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPageViewControllerClassReferenceClassRef/

54
58
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
58