【Swift】Swift入門 ~ UIPageViewControllerを使ってみる ~

2020.03.13

どうも、むつたくです。

今回は「UIPageViewController」について紹介していきます。
また画面遷移系ですが、アプリ初回起動時とかにアプリ説明(チュートリアル)をこのUIPageViewControllerを使って表示させたり、
読書アプリなんかの電子書籍リーダにも使用でき、用途は様々です。

 

 

前回までのSwift記事

【Swift】Swift入門 ~ UITableViewを使ってみる ~
【Swift】Swift入門 ~ NavigationControllerを使ってみる ~
【Swift】Swift入門 ~ presentの画面遷移 ~

 

今回やること

 

公式ドキュメントは以下です。
UIPageViewController
UIPageControl

開発環境

  • macOS Catalina(10.15.3)
  • Xcode 11.3
  • Swift 5.1
  • Simulater iPhone 11Pro
  • iOS 13.2

 

今回も、XcodeのProjectで、SingleViewAppとして作成します。

 

基本的な使用方法


StoryBoardからの使用方法 – コントローラの配置

まずは、StoryBoardから設定していく方法から紹介します。

  1. StoryBoard上に「PageViewController」を追加。
  2. PageViewControllerを選択し、「Is Initial ViewController」をチェック。
  3. PageViewController.swiftファイルを追加。

 

 

 

次に遷移先のViewControllerを2枚ほど配置していきます。
2枚目のStoryBoradIDを[ SecondView ]に、3枚目を[ ThirdView ]にします。遷移の見分けも可能な様に背景色を設定します。

ついでに追加したViewControllerのClassファイルを作成しましょう。
[ Cocoa Touch Class > Class:(SecondViewController) > SubclassOf:(UIViewController) ]
3枚目は[ Class:(ThirdViewController) ]にします。他は同じです。

 

そうしましたら、追加した2ファイルをSecondView及び、ThirdViewに関連付けします。
Use StoryBoardID」のチェックも忘れずに。

これでStoryBoradはOKです。次にコードです。

StoryBoardからの使用方法 – コーディング

主に処理を記載するのは最初に追加した「PageViewController.swift」になります。

下記コードが、動くモノになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// PageViewController.swift
import UIKit

class PageViewController: UIPageViewController {

    // ① PageViewで表示するViewControllerを格納する配列を定義
    private var controllers: [UIViewController] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        self.initPageViewController()
    }
   
    /// PageViewControllerの初期化処理
    private func initPageViewController() {

        // ② PageViewControllerで表示するViewControllerをインスタンス化する
        let firstVC = storyboard!.instantiateViewController(withIdentifier: "FirstView") as! ViewController
        let secondVC = storyboard!.instantiateViewController(withIdentifier: "SecondView") as! SecondViewController
        let ThirdVC = storyboard!.instantiateViewController(withIdentifier: "ThirdView") as! ThirdViewController

        // ③ インスタンス化したViewControllerを配列に保存する
        self.controllers = [ firstVC, secondVC, ThirdVC ]

        // ④ 最初に表示するViewControllerを指定する
        setViewControllers([self.controllers[0]], direction: .forward, animated: true, completion: nil)
       
        // ④ PageViewControllerのDataSourceを関連付ける
        self.dataSource = self
    }

}

// ⑤ PageViewControllerのDataSourceを定義
// MARK: - UIPageViewController DataSource
extension PageViewController: UIPageViewControllerDataSource {

    /// ページ数
    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return self.controllers.count
    }
   
    /// 左にスワイプ(進む)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        if let index = self.controllers.firstIndex(of: viewController),
            index < self.controllers.count - 1 {
            return self.controllers[index + 1]
        } else {
            return nil
        }
    }

    /// 右にスワイプ (戻る)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        if let index = self.controllers.firstIndex(of: viewController),
            index > 0 {
            return self.controllers[index - 1]
        } else {
            return nil
        }
    }

}

 

コード①

PageViewControllerでは、表示するViewControllerを配列で管理するため、クラス内グローバル関数を定義します。

 

コード②

次は、表示対象のViewControllerをインスタンス化します。

 

コード③

次に、最初に定義した配列にインスタンス化したViewControllerを格納します。
この時、表示順に格納していくのがポイントです。インデックスのインクリメント、デクリメントで表示を切り替えるためです。

 

コード④

次は、最初に表示する画面を設定します。設定するプロパティは[ setViewControllers ]です。

  • 第一引数: 最初に表示するViewController
  • 第二引数: ナビゲーションの指定
  • 第三引数: アニメーション有無
  • 第四引数: コールバック関数

詳しくは公式ドキュメントを確認してください。
setViewControllers(_:direction:animated:completion:)

 

コード⑤

最後に、PageViewControllerのDataSourceを関連付けます。
DataSourceでやっていることは、ページ数の上限設定、左右スワイプの挙動になります。
ページ数の上限は[ presentationCount ]で行います。配列のインデックス数を指定してあげればOKです。

 

左スワイプの挙動は[ pageViewController(viewControllerAfter) ]で行います。引数内にAfterが付いている関数です。
右スワイプの挙動は[ pageViewController(viewControllerBefore) ]で行います。引数内にBeforeが付いている関数です。

 

やっていることは単純です。
左スワイプ検知時、最初に定義した配列での現在表示しているViewControllerのインデックスを取得し、上限値を超過していなければ、次ページのインデックスを渡しています。
右スワイプ検知時は、同じく表示中のViewControllerのインデックスを取得し、0未満(最初のページ)ではない時に、前ページのインデックスを渡してます。

 

以上がStoryBoardを使用した場合です。

コードからの実装

コードからの実装とStoryBoardからの実装の違いは、PageViewControllerのインスタンスをコード上に書くことぐらいでしょうか。
ViewControllerをStoryBoard上に配置しないのであれば、こちらも作成していく必要がありますが。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// ViewController.swift
import UIKit

class ViewController: UIViewController {

    // ① PageViewControllerクラス、
    //   PageViewで表示するViewControllerを格納する配列をそれぞれ定義
    private var pageViewController: UIPageViewController!
    private var controllers: [ UIViewController ] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        self.initPageViewController()
    }

    private func initPageViewController() {
       
        // 背景色定義
        let backColor: [ UIColor ] = [ .systemIndigo, .systemOrange, .systemGreen ]

        // ② 表示するViewController作成 & 表示配列に保存
        for i in 0 ... 2 {
            let myViewController: UIViewController = UIViewController()
            myViewController.view.backgroundColor = backColor[i]
            myViewController.view.frame = self.view.frame
            self.controllers.append(myViewController)
        }
        // ③ UIPageViewController設定
        self.pageViewController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil)
        self.pageViewController.setViewControllers([self.controllers[0]], direction: .forward, animated: true, completion: nil)
        self.pageViewController.dataSource = self
       
        // ④既存ViewControllerに追加
        self.addChild(self.pageViewController)
        self.view.addSubview(self.pageViewController.view!)
    }
}

// ⑤ PageViewControllerのDataSourceを定義
// MARK: - UIPageViewController DataSource
extension ViewController: UIPageViewControllerDataSource {

    /// ページ数
    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return self.controllers.count
    }
   
    /// 左にスワイプ(進む)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        if let index = self.controllers.firstIndex(of: viewController),
            index < self.controllers.count - 1 {
            return self.controllers[index + 1]
        } else {
            return nil
        }
    }

    /// 右にスワイプ (戻る)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        if let index = self.controllers.firstIndex(of: viewController),
            index > 0 {
            return self.controllers[index - 1]
        } else {
            return nil
        }
    }

}

コード①

最初に、[ UIPageViewContorller ]のクラスを作成します。

 

コード②

次に、表示するViewControllerを作成します。使い捨てになるので、インスタンス生成&破棄を容易に行えるfor文中に記載した方が良いでしょう。
電子書籍リーダとかは、ここにUIImageを配置して、画像を読み込ませる処理をするとかですね。
コードでラベル配置したり、ボタン配置したりと色々するのであれば、StoryBoardからやった方が早いと思います。

 

コード③

次は、UIPageViewControllerのインスタンス生成です。最初に定義してあげただけなので、生成します。
ここで、遷移アニメーションやナビゲーション方向を決めます。

 

コード④

次に、既存のViewControllerにPageViewControllerを追加すればOKです。

 

最後にPageViewDataSourceを定義してあげればOKです。これはStoryBoardで定義したコードと同一になります。

PageControllerの使用方法

PageController

 

上部画像の「この部分」と吹き出しにした枠の箇所がPageControllerになります。
現在ページの場所と、全ページ数が視覚的にわかるコントローラです。
また、PageControllerですが、StoryBoardで配置するよりコードから作成した方が簡単なので、紹介はコードからのみにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// ViewController.swift
class ViewController: UIViewController {

    private var pageViewController: UIPageViewController!
    private var controllers: [ UIViewController ] = []

    // ① UIPageControlクラスを定義
    private var pageControl: UIPageControl!
   
    override func viewDidLoad() {
        super.viewDidLoad()

        self.initPageViewController()

        // UIPageControlの初期化メソッドを作成
        self.setPageControl()
    }

    private func initPageViewController() {
        let backColor: [ UIColor ] = [ .white, .systemOrange, .systemGreen ]

        for i in 0 ... 2 {
            let myViewController: UIViewController = UIViewController()
            myViewController.view.backgroundColor = backColor[i]
            myViewController.view.frame = self.view.frame
            self.controllers.append(myViewController)
        }

        self.pageViewController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil)
        self.pageViewController.setViewControllers([self.controllers[0]], direction: .forward, animated: true, completion: nil)
        self.pageViewController.dataSource = self

        // ① delegate追加
        self.pageViewController.delegate = self
       

        self.addChild(self.pageViewController)
        self.view.addSubview(self.pageViewController.view!)
    }

    // ② [ setPageControl ]メソッド追加
    private func setPageControl() {
       
        // PageControlの配置場所
        self.pageControl = UIPageControl(frame: CGRect(x: 0,y: UIScreen.main.bounds.maxY - 190, width: UIScreen.main.bounds.width,height: 50))
     // 全ページ数
        self.pageControl.numberOfPages = self.controllers.count
        // 表示ページ
        self.pageControl.currentPage = 0
        // インジケータの色
        self.pageControl.pageIndicatorTintColor = .gray
        // 現在ページのインジケータの色
        self.pageControl.currentPageIndicatorTintColor = .red

        self.view.addSubview(self.pageControl)
    }

}

// MARK: - UIPageViewController DataSource
extension ViewController: UIPageViewControllerDataSource {
  // 省略
}

// ③ [ UIPageViewControllerDelegate ]追加
// MARK: - UIPageViewController Delegate
extension ViewController: UIPageViewControllerDelegate {

    // ④ アニメーション終了後処理 追加
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        let currentPage = pageViewController.viewControllers![0]
        self.pageControl.currentPage = self.controllers.firstIndex(of: currentPage)!
    }

}

 

コード①

まずは、PageControllerのカレントページを切り替えるために、PageViewControllerDelegeteを追加します。
Delegateを追加しなくても管理はできますが、左右スワイプのイベントでも管理できますが、似た処理を別々のメソッドに記載するので、管理が煩雑になるのを防ぐため、Delegateで管理します。

 

コード②

次に、PageControllerの初期化を行います。最低限の配置場所全ページ数(初期)表示ページインジケータ色現在ページのインジケータ色を設定すれば問題ないと思います。

 

コード③

次に、カレントページ切り替えのために、UIPageViewControllerのDelegateを追加します。

 

コード④

最後に、切り替えをするためのイベントを追加します。UIPageViewControllerのアニメーション終了後が妥当かと思いますので、[ pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) ]を追加します。

 

これで、ページを遷移した際にインジケータの色が変更されます。

 


 

UIPageViewController及びUIPageControlの使い方は以上になります。
それではまたの機会に!


Top