UIPageViewControllerのエンドレス化などなど

UIPageViewControllerをエンドレスでページ切り替えする方法とかその他小ネタ

エンドレスの挙動は、最後のページから次へ進むと最初のページへ、最初のページからさらに戻ると最後のページへ移動する感じ。 詳細はサンプルを参照のこと

作成の流れ

まず、画面の構造は以下の通り。ページ部分はChildViewControllerにしているが、 UIPageViewControllerを全画面にしている場合も同じ

ViewController
 └─ Container View
     └─ UIPageViewController
         └─ (各ページ)

ページの生成

今回はページ数が固定(5ページ)を想定しているので、viewDidLoadでページのリストをあらかじめ生成しておく

// var pages = [PageViewController]()
pages = Array(1...5).flatMap {
    guard let vc = storyboard?.instantiateViewController(withIdentifier: "Page") as? PageViewController else {
        return nil
    }
    // 各ページの設定
    return vc
}

リストを生成したら、初期表示するページを設定

setViewControllers([pages[0]], direction: .forward, animated: false)

ページングの処理

UIPageViewControllerDataSourceviewControllerBeforeviewControllerAfterを実装する

viewControllerBeforeだとこんな感じで前のページを返す

guard let vc = viewController as? PageViewController,
    var index = pages.index(of: vc)  else { return nil }

index -= 1
index = (index < pages.startIndex) ? pages.endIndex - 1 : index
return pages[index]

ポイントは基準となるページ(引数で渡されるviewController)のインデックスをページのリストから検索して見つけること (コードではindex = pages.index(of: vc))

例えば、現在表示中のページのインデックスをプロパティに持っておく方法ではうまくいかない。 なぜならこのデリゲートは現在表示中のページ以外からも呼び出されるので

小ネタ

UIPageViewControllerのStoryboardへの追加

追加する時は、右下のUIパーツが並んでいるところからPageViewControllerを選んで追加すること
通常のViewControllerで追加してしまうと後から変更しても、UIPageViewControllerの設定項目が出てこない。。。

ページを表示した時に各UIの位置が一瞬ずれて表示されてしまう場合

AutoLayoutの基準がLayoutGuideになっているとダメな場合があるのでViewを基準に変えてみる
(例えば上部はTopLayoutGuideではなく、Viewからにする。Constrain to marginsはチェックを外す)

Container Viewの中のViewControllerを取得する

Container ViewChildViewControllerを繋ぐseguedestinationから取得できる

private weak var pager: PagerViewController!

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.destination {
    case let vc as PagerViewController:
        pager = vc
    default: break
    }
}

ちなみに、今回はContainer ViewPagerViewControllerは1つなので上記の判定だが、 同じクラスのものが複数ある場合は、segueidentifierで判定すれば良い (もちろん、Storyboard上で別々のIDを設定しておく)

開発環境

ソース

こちら