はじめに
この記事はnitkitコンピュータ研究部AdventCalender2019,二日目の記事です
チュートリアル(walkthrough)にボタンでのページ移動を実装したかったのですが,良い記事が見つからなかったのでメモの意味も込めてこの記事を書いてます.
この記事でできること
- UIPageViewControllerの実装
- UIPageViewControllerにボタンを使用したページ遷移の実装
完成品
GitHubソースコード: https://github.com/tessy0901/test_walkthrough
実装方法
UIPageViewControllerの実装
基本的にこちらの記事を参考にしつつ,swift4以降に対応させていきます.
UIPageViewController
1.project作成
お好みでCreate Git repository on my mac
にチェックを入れてCreate
2.PageViewControllerの実装(StoryBoard編)
Is Initial View Controller(StoryBoardの矢印)
をPageViewController
に設定する
それぞれに対応したファイルを作成する
同様の手順でBViewController.swift
,CViewController.swift
を作成します.
3.PageViewControllerの実装(ViewController編)
1.まずPageViewController
というswiftファイルを作り,StoryBoardのPageControllに紐付けする.
この時,SubclassはUIPageViewController
としておきます.
2.中身を以下のようにする
import UIKit
class PageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
dataSource = self
let pageViewA = storyboard!.instantiateViewController(withIdentifier: "AViewController") as! AViewController
self.setViewControllers([pageViewA], direction: .forward, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
setViewControllers()
メソッドで最初のViewを読み込ませている
3.dataSource
を使い,前後のページの読み込みを行うようにする.
この時,複数のViewを配列でまとめておきます.
また,現在のページを示す変数も用意しておきます.
import UIKit
class PageViewController: UIPageViewController {
var pageViewControllers: [UIViewController] = []
var nowPage: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
//インスタンス化
let pageViewA = storyboard!.instantiateViewController(withIdentifier: "AViewController") as! AViewController
let pageViewB = storyboard!.instantiateViewController(withIdentifier: "BViewController") as! BViewController
let pageViewC = storyboard!.instantiateViewController(withIdentifier: "CViewController") as! CViewController
pageViewControllers = [pageViewA, pageViewB, pageViewC]
//最初に表示するページの指定
self.setViewControllers([pageViewControllers[0]], direction: .forward, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
//以下追加
extension PageViewController : UIPageViewControllerDataSource, UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
return nil
}
}
追加した,
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController)
と,
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController)
がそれぞれ現在のページの前と後ろのページの指定になります.
4.前後ページの指定の実装
ページをまとめた配列のPageViewControllers
と,現在のページを表すnowPage
を使い,ページが変わるごとに前後のページを更新する動作を実装します.
下記の表のように実装していきます.
前のページ | 現在のページ | 次のページ |
---|---|---|
nil | 0 | nowPage+1 |
nowPage-1 | 0<現在のページ<ページ数-1 | nowPage+1 |
nowPage-1 | ページ数-1 | nil |
メソッドを以下のように変更します.
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let index = pageViewControllers.firstIndex(of: viewController)
if index == 0 {
return nil
} else {
return pageViewControllers[index!-1]
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let index = pageViewControllers.firstIndex(of: viewController)
if index == pageViewControllers.count - 1 {
return nil
} else {
return pageViewControllers[index!+1]
}
}
ここまで実装できたらビルドして動作を確認します.
スワイプでページの移動はできるようになったものの,ページ数の表示ができていないのが確認できたと思います.
5.pageControl(ページ数や現在ページの表示)の実装
こちらの2つを使います.
func presentationCount
func presentationIndex
まずclass PageController
内で,ページ番号を管理する変数を宣言します.
そして上記二つのメソッドをextensionに追加し,ページ数と現在のページ数の表示を行います,
class PageViewController: UIPageViewController {
var pageViewControllers: [UIViewController] = []
var nowPage: Int = 0
//var currentPageでページ番号を管理
var currentPage = 0
//以下省略~~~~~~~~~~~~~~
extension PageViewController : UIPageViewControllerDataSource, UIPageViewControllerDelegate {
//全ページ数を返すメソッド
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return pageViewControllers.count
}
//現在のページを返すメソッド
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return currentPage
}
//以下省略~~~~~~~~~~~~~~
これで画面の下にpageControlが出たかと思われます.
ボタンによる遷移の実装
いよいよボタンの実装に入ります.
StoryBoardにボタンを配置する.
ViewControllerに紐付ける.
今回はnextMove
という名前をつけて紐付けました.
ボタンの動作を記述する
クロージャを使ってボタンがタップされた時に,onButtonTapped
が呼び出される
→onButtonTapped
が呼び出された時に次のviewを指定して呼び出す.
という処理を書いていきます.
import UIKit
class AViewController: UIViewController {
var onButtonTapped: () -> Void = {}
@IBAction func nextMove(_ sender: Any) {
onButtonTapped()
}
}
同様にBViewController.swift
,BViewController.swift
にも追加します.
import UIKit
class PageViewController: UIPageViewController {
var pageViewControllers: [UIViewController] = []
var nowPage: Int = 0
var currentPage = 0
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
dataSource = self
//インスタンス化
let pageViewA = storyboard!.instantiateViewController(withIdentifier: "AViewController") as! AViewController
let pageViewB = storyboard!.instantiateViewController(withIdentifier: "BViewController") as! BViewController
let pageViewC = storyboard!.instantiateViewController(withIdentifier: "CViewController") as! CViewController
pageViewControllers = [pageViewA, pageViewB, pageViewC]
//最初に表示するページの指定
self.setViewControllers([pageViewControllers[0]], direction: .forward, animated: true, completion: nil)
//ここから
pageViewA.onButtonTapped = {
self.setViewControllers([self.pageViewControllers[1]], direction: .forward, animated: true, completion: nil)
}
pageViewB.onButtonTapped = {
self.setViewControllers([self.pageViewControllers[2]], direction: .forward, animated: true, completion: nil)
}
pageViewC.onButtonTapped = {
self.setViewControllers([self.pageViewControllers[0]], direction: .forward, animated: true, completion: nil)
}
}
//ここまで追加
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
これでボタンの遷移はできるようになったはずですが,下のPageControlが動いていないと思うので動くようにしていきます.
なおスワイプではちゃんと動いているはずですのでそれも確認しておきましょう.
pageControlを動作させる
ページが変わった時にはpresentationIndex
メソッドが呼び出されるのですが,そこにはcurrentPage
が返されるように記述しているはずです.
ですので,ボタンをタップした時に次のcurrentPage
を指定してあげれば実装できるでしょう.
import UIKit
class PageViewController: UIPageViewController {
var pageViewControllers: [UIViewController] = []
var nowPage: Int = 0
var currentPage = 0
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
dataSource = self
//インスタンス化
let pageViewA = storyboard!.instantiateViewController(withIdentifier: "AViewController") as! AViewController
let pageViewB = storyboard!.instantiateViewController(withIdentifier: "BViewController") as! BViewController
let pageViewC = storyboard!.instantiateViewController(withIdentifier: "CViewController") as! CViewController
pageViewControllers = [pageViewA, pageViewB, pageViewC]
//最初に表示するページの指定
self.setViewControllers([pageViewControllers[0]], direction: .forward, animated: true, completion: nil)
pageViewA.onButtonTapped = {
//追加
self.currentPage = 1
self.setViewControllers([self.pageViewControllers[1]], direction: .forward, animated: true, completion: nil)
}
pageViewB.onButtonTapped = {
//追加
self.currentPage = 2
self.setViewControllers([self.pageViewControllers[2]], direction: .forward, animated: true, completion: nil)
}
pageViewC.onButtonTapped = {
//追加
self.currentPage = 0
self.setViewControllers([self.pageViewControllers[0]], direction: .forward, animated: true, completion: nil)
}
}
これでビルドするとボタンを押した時にpageControlも移動してるのが確認できたと思います.
最後に
今回は表示するViewごとにそれぞれswiftファイルを作成しましたが,ボタン遷移だけなら一つのviewを使いまわしても問題ないです.
もっといい書き方があると思うので,コメントいただけるとありがたいです.
なお,3番目のviewにボタンがないのは寂しい(?)という理由で,ボタンをタップした時のみループするようにしています.
次はUIScrolViewを使って同じ機能の実装方法を紹介したいと思います.
参考文献
【決定版】UIPageViewControllerの使い方(Swift)
presentationCount(for:) (Apple Developer Document)
presentationIndex(for:) (Apple Developer Document)
【Swift】クロージャ(基本編)