CrossBridge Lab

技術ネタ、デバイスネタを...

【Swift3】 ContainerView と UIPageViewController を使って画面の一部をページングさせる

2016/11/19 修正 サンプルをSwift3に対応させました

はじめに

 本記事では ContainerView と UIPageViewController を使って画面の一部をページングさせる方法を解説します。言語は Swift です。UIPageViewController のインスタンスから View を取得して frame を設定したり、ソースコードで Constraints を設定して Autolayout でいい感じに表示させたりすることでも実現可能ですが、ContainerView を使うことで Storyboard 上で画面上に任意のサイズで UIPageViewController (の View) を表示させることが可能になります。

UIPageViewController とは

 まず UIPageViewController とは何?というのを簡単に説明します。UIPageViewController は以下のような機能を持っています。

  • スワイプでページを切り替える
  • 任意のページに移動する
  • 切替時にページがスクロールするようなアニメーションを付ける
  • 切替時にページをめくるようなアニメーション(カール)を付ける
  • 見開きで表示する(2ページを1画面に表示する)

 Xcode で新規にプロジェクトを作成する時に Page-Based Application という名前のテンプレートを使うと UIPageViewController を使ったプロジェクトが作成されます。

f:id:crossbridge-lab:20151226205422p:plain

UIPageViewController の基本的な使い方

 Page-Based Application テンプレートを使ったプロジェクトを作成して中を見ると分かりますが、UIPageViewController の基本的な使い方は以下のとおりです。

  1. UIPageViewControllerDataSource プロトコルを実装する
  2. 必要であれば UIPageViewControllerDelegate プロトコルを実装する
  3. UIPageViewController のインスタンス生成して、上記のプロトコルを実装したクラスのインスタンスを設定する
  4. setViewControllers メソッドで最初に表示するページを設定する
  5. UIPageViewController の View を親の ViewController の View に add する
  6. addChildViewController メソッドで UIPageViewController のインスタンスを親の ViewController に関連付けてから didMoveToParentViewController メソッドを呼び出す

 Page-Based Application テンプレートだと frame を直接設定していますが、frame のサイズを直接設定するのが嫌なので・・・ContainerView を使って画面の一部に UIPageViewController を表示させることを 今から説明していきます。

ContainerView と UIPageViewController を使って画面の一部をページングさせる

 さて、ここから本題です。以下の手順で進めます。

  1. ContainerView に EmbedSegue で UIPageViewController を接続する
  2. ページングして表示させる ViewController を準備する
  3. ContainerView に Embed した UIPageViewController にもろもろ設定する
  4. UIPageViewControllerDataSource プロトコルを実装する
  5. ボタンをタップした時に特定のページにジャンプさせる

 解説に使っているサンプルコードはこちらです。

github.com

1. ContainerView に EmbedSegue で UIPageViewController を接続する

 まず Storyboard 上で ContainerView を配置します。勝手に ViewController も追加されて EmbedSegue で繋がれているのでその ViewController を削除します。そして、UIPageViewController を Storyboard 上に配置して、ContainerView から EmbedSegue を繋ぎ直します。

f:id:crossbridge-lab:20151226205513p:plain

ContainerView とは?というのはこちらで。

crossbridge-lab.hatenablog.com

2. ページングして表示させる ViewController を準備する

 次にページングして表示させる ViewController を準備します。Storyboard に配置した DataViewController クラスを10個インスタンス化し、配列 vcArray に追加していきます。正しくページング出来ているか分かりやすくするために UILabel に 0 から 9 を表示させます。

■ ViewController.swift viewDidLoadメソッド内

// Storyboard 上に配置した ViewController(StoryboardID = DataViewController) をインスタンス化して配列に追加する
for index in 0...9 {
    let vc = storyboard?.instantiateViewController(withIdentifier: "DataViewController") as! DataViewController
    vc.labelStr = index.description
    vcArray.append(vc)
}

3. ContainerView に Embed した UIPageViewController にもろもろ設定する

 ページングして表示させる ViewController を準備したら UIPageViewController に表示の設定などを行います。ContainerView に Embed した UIPageViewController は childViewControllers の先頭を取り出すことで取得できます。 ※先頭なのは ContainerView が1つだからなので注意して下さい。常に childViewControllers[0] で取得できるわけではありません

■ ViewController.swift viewDidLoadメソッド内

// ContainerView に Embed した UIPageViewController を取得する
pageViewController = childViewControllers[0] as? UIPageViewController

// dataSource を設定する
pageViewController!.dataSource = self

// 最初に表示する画面として配列の先頭の ViewController を設定する
pageViewController!.setViewControllers([vcArray[0]], direction: .forward, animated: false, completion: nil)

4. UIPageViewControllerDataSource プロトコルを実装する

 UIPageViewController でページングを行う際に表示する ViewController を返すための UIPageViewControllerDataSource プロトコルを実装します。サンプルではデフォオルトで表示する ViewController に実装していますが、UIPageViewController クラスを継承したクラスを用意してそのクラスで実装しても良いですし、全く別のモデルクラスを用意するという方法も考えられます。

 実装が必須のメソッドは以下の2つです。それぞれ順送りした時、逆順に送った時に呼ばれるメソッドです。端っこまでいき、ページさせたくないときは nil を返すようにします。これらのメソッドの実装次第でページングをループさせることも可能です。

  • pageViewController(_:viewControllerBeforeViewController:)メソッド
  • pageViewController(_:viewControllerAfterViewController:)メソッド

■ ViewController.swift pageViewController(_:viewControllerBeforeViewController:)メソッド

// 逆方向にページ送りした時に呼ばれるメソッド
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    
    guard let index = vcArray.index(of: viewController as! DataViewController), index > 0 else {
        return nil
    }
    
    return vcArray[index - 1]
}

■ ViewController.swift pageViewController(_:viewControllerAfterViewController:)メソッド

// 順方向にページ送りした時に呼ばれるメソッド
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
    
    guard let index = vcArray.index(of: viewController as! DataViewController), index < vcArray.count - 1 else {
        return nil
    }
    
    return vcArray[index + 1]
}

5. ボタンをタップした時に特定のページにジャンプさせる

 ここまでの実装でページングさせることは完了ですが、画面上部に配置したボタンをタップしたときに特定のページ(0,5,9)にジャンプさせてみようと思います。3つのボタンの Action として handleButton(_:)メソッドを設定しています。

■ ViewController.swift handleButton(_:)メソッド

// ボタンがタップされたらそれぞれ 0ページ、5ページ、9ページ目にジャンプさせる
@IBAction func handleButton(_ sender: UIButton) {
    switch sender.tag {
    case 0:
        pageViewController!.setViewControllers([vcArray[0]], direction: .forward, animated: false, completion: nil)
    case 1:
        pageViewController!.setViewControllers([vcArray[5]], direction: .forward, animated: false, completion: nil)
    case 2:
        pageViewController!.setViewControllers([vcArray[9]], direction: .forward, animated: false, completion: nil)
    default:
        break
    }
}

実行してみる

 サンプルを実行してみます。左右にフリックするとページが切り替わり、画面上部のボタンをタップしたら任意のページに移動します。

f:id:crossbridge-lab:20151226205448p:plain

まとめ

  ContainerView と UIPageViewController を使うことで画面の一部をページングさせることができます。ソースコードで Constraints を設定する必要がなく、Storyboard 上で ContainerView の表示を設定することで UIPageViewController のサイズ、表示位置を決定することができます。

 今回作ったサンプルは GitHub にアップしています。

github.com