目的
- よく使うであろう(?)UIPageViewControllerを使用したアプリをStoryboardありとなしでサクッと作れるようになる
- ありなしの両方を試すことで、Storyboardが何をやっているのかなんとなくイメージできるようになる
Step1. コードのみでUIPageViewController管理化
前回のコード をベースに作業。
今回もなるべく爆速でUIPageViewControllerを利用するため、ひとまずStoryboardではなくコード側で実装してみる。
すでにUITabBarControllerで管理されたUINavigationControllerと無名UIViewControllerがあるため、これをUIPageViewControllerに置き換えてみる
- UITabBarController
- tabBarItem1: UINavigationController
- stack[]
- ViewController(TableView)
- ... add押下でViewControllerが複製されてstackに積まれていく
- stack[]
- tabBarItem2: UIViewController ← ココをUIPageViewControllerに
- tabBarItem1: UINavigationController
1.1. PageViewControllerクラスの追加
swiftクラスファイルを追加し「PageViewController.swift」として保存する。
とりあえず固定3ページで単にページ番号を表示するだけのViewを表示するようなイメージで以下。
import UIKit
class PageViewController:
UIViewController,
UIPageViewControllerDelegate,
UIPageViewControllerDataSource
{
var pages: [PageChildViewController] = [
PageChildViewController(pageIndex: 0),
PageChildViewController(pageIndex: 1),
PageChildViewController(pageIndex: 2),
]
override func viewDidLoad() {
let pc: UIPageViewController = UIPageViewController(transitionStyle: .scroll,
navigationOrientation: .horizontal)
pc.dataSource = self
pc.delegate = self
pc.setViewControllers([self.pages[0]],
direction: .forward,
animated: false,
completion: nil)
self.addChild(pc)
self.view.addSubview(pc.view)
}
// UIPageViewDataSource
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let child: PageChildViewController = viewController as! PageChildViewController
let beforePageIndex: Int = child.pageIndex
if 0 == beforePageIndex {
return nil
}
return self.pages[beforePageIndex - 1]
}
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let child: PageChildViewController = viewController as! PageChildViewController
let afterPageIndex: Int = child.pageIndex
if self.pages.count <= afterPageIndex + 1 {
return nil
}
return self.pages[afterPageIndex + 1]
}
}
class PageChildViewController: UIViewController {
var pageIndex: Int = 0
required init?(coder aDecoder: NSCoder) {
fatalError("not implemented")
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
convenience init(pageIndex: Int) {
self.init(nibName: nil, bundle: nil)
self.pageIndex = pageIndex
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
let label: UILabel = UILabel(frame: view.bounds)
label.text = "page:\(pageIndex)"
label.textAlignment = .center
view.addSubview(label)
}
}
ポイントとしては、
- UITableViewControllerとは異なり、addSubViewするだけでなく、子ViewControllerとしてUIPageViewControllerを格納する必要がある
- viewControllerBeforeで前のpageに戻った際のUIViewController、viewControllerAfterで次のpageに進んだ際のUIViewControllerを返却してやる
- インスタンス変数などでpageIndexを管理したくなるが、必ずしも同期がとれるわけではないので(内部pageIndexのみが変更し、実際の画面がスワイプしきれない場合がある)、必ず引数のviewControllerAfterやviewControllerBeforeに格納されている情報を利用する
- 今回は全てのpageにそのpage自体のpageIndexを持たせているのでこちらを使用している
1.2. AppDelegateの修正
続いてAppDelegateの修正。
上記の通り、すでにTabBarやNavigtationの設定は済んでいる前提。
--- a/PracticeApp/AppDelegate.swift
+++ b/PracticeApp/AppDelegate.swift
@@ -17,6 +17,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
+ let tabc: UITabBarController = window?.rootViewController as! UITabBarController
+ var newVcs: [UIViewController] = []
+
+ // tab1: navigation controller
+ for vc in tabc.viewControllers! {
+ if vc is UINavigationController {
+ newVcs.append(vc)
+ }
+ }
+
+ // tab2: pageview controller
+ let pageVc: PageViewController = PageViewController()
+ pageVc.tabBarItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 1)
+ newVcs.append(pageVc)
+
+ tabc.setViewControllers(newVcs, animated: true)
return true
}
UIPageViewControllerの設定自体はPageViewController内で実装しているので、ここでのポイントはあまりない。
Storyboard側で設定しているtabBar要素からUINavigationControllerの部分だけを再利用し、新たに自作のPageViewControllerを追加してtabに設定している。
1.3. 起動
Runすると、tabとnaviとpageが組み合わさった適当アプリが動く。
Step2. 動的paging+UIちょっと工夫
スワイプする分だけ(理論上)無限にpageが変わるように変更する。
また、ちょっと見た目もわかりやすく背景色をpage数によって変えてみる。
2.1. PageViewControllerの修正
以下のように修正
--- a/PracticeApp/PageViewController.swift
+++ b/PracticeApp/PageViewController.swift
@@ -15,8 +15,6 @@ class PageViewController:
{
var pages: [PageChildViewController] = [
PageChildViewController(pageIndex: 0),
- PageChildViewController(pageIndex: 1),
- PageChildViewController(pageIndex: 2),
]
override func viewDidLoad() {
@@ -42,7 +40,7 @@ class PageViewController:
if 0 == beforePageIndex {
return nil
}
- return self.pages[beforePageIndex - 1]
+ return getPage(pageIndex: beforePageIndex - 1)
}
func pageViewController(_ pageViewController: UIPageViewController,
@@ -50,10 +48,21 @@ class PageViewController:
{
let child: PageChildViewController = viewController as! PageChildViewController
let afterPageIndex: Int = child.pageIndex
- if self.pages.count <= afterPageIndex + 1 {
+ return getPage(pageIndex: afterPageIndex + 1)
+ }
+
+ // private
+
+ func getPage(pageIndex: Int) -> PageChildViewController?
+ {
+ if pageIndex < 0 {
return nil
}
- return self.pages[afterPageIndex + 1]
+ while self.pages.count <= pageIndex {
+ let vc: PageChildViewController = PageChildViewController(pageIndex: self.pages.count)
+ self.pages.append(vc)
+ }
+ return self.pages[pageIndex]
}
}
@@ -76,8 +85,22 @@ class PageChildViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
- view.backgroundColor = UIColor.white
+ // background color
+ var r = 0.1 * CGFloat(self.pageIndex)
+ while 1 < r {
+ r -= 1
+ }
+ var g = 0.2 * CGFloat(self.pageIndex)
+ while 1 < g {
+ g -= 1
+ }
+ var b = 0.3 * CGFloat(self.pageIndex)
+ while 1 < b {
+ b -= 1
+ }
+ self.view.backgroundColor = UIColor(red: r, green: g, blue: b, alpha: 1)
+ // label
let label: UILabel = UILabel(frame: view.bounds)
label.text = "page:\(pageIndex)"
label.textAlignment = .center
ポイントとしては
- pagesでPageChildViewControllerのインスタンスを管理(再利用のみ、特に破棄などはしない)
- PageChildControllerはコンストラクト時の引数pageIndexに応じてbackgroundColorを変更
2.2. 起動
Step3. Storyboardを使ってみる
これまで通り、今記述した部分をなるべくStoryboardにお任せする
3.1. UIPageViewControllerをStoryboardへ移行
TabBarControllerのoutletsの「view controllers」とUIPageViewControllerをバインドする
tabBarItemのSystem Itemsも設定しておく
PageViewControllerの諸々の属性も設定しておく
3.2. 子ページコントローラ(PageChildViewController)をStoryboardへ
新規にUIViewControllerを追加し、UILabelなどを配置
IBOutletで各Labelをバインドするほか、クラス設定、Storyboard IDも忘れずに設定しておく
3.3. コード修正
これに対応して以下のようにコード修正を行う
--- a/PracticeApp/PageViewController.swift
+++ b/PracticeApp/PageViewController.swift
@@ -9,25 +9,23 @@
import UIKit
class PageViewController:
- UIViewController,
+ UIPageViewController,
UIPageViewControllerDelegate,
UIPageViewControllerDataSource
{
var pages: [PageChildViewController] = [
- PageChildViewController(pageIndex: 0),
+ PageChildViewController.getInstance(pageIndex: 0),
]
override func viewDidLoad() {
- let pc: UIPageViewController = UIPageViewController(transitionStyle: .scroll,
- navigationOrientation: .horizontal)
- pc.dataSource = self
- pc.delegate = self
- pc.setViewControllers([self.pages[0]],
- direction: .forward,
- animated: false,
- completion: nil)
- self.addChild(pc)
- self.view.addSubview(pc.view)
+ super.viewDidLoad()
+
+ delegate = self
+ dataSource = self
+ setViewControllers([self.pages[0]],
+ direction: .forward,
+ animated: false,
+ completion: nil)
}
// UIPageViewDataSource
@@ -59,7 +57,7 @@ class PageViewController:
return nil
}
while self.pages.count <= pageIndex {
- let vc: PageChildViewController = PageChildViewController(pageIndex: self.pages.count)
+ let vc = PageChildViewController.getInstance(pageIndex: self.pages.count)
self.pages.append(vc)
}
return self.pages[pageIndex]
@@ -67,19 +65,19 @@ class PageViewController:
}
class PageChildViewController: UIViewController {
- var pageIndex: Int = 0
-
- required init?(coder aDecoder: NSCoder) {
- fatalError("not implemented")
- }
+ @IBOutlet weak var pageIndexLabel: UILabel!
+ @IBOutlet weak var redValueLabel: UILabel!
+ @IBOutlet weak var greenValueLabel: UILabel!
+ @IBOutlet weak var blueValueLabel: UILabel!
- override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
- super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
- }
+ var pageIndex: Int = 0
- convenience init(pageIndex: Int) {
- self.init(nibName: nil, bundle: nil)
- self.pageIndex = pageIndex
+ static func getInstance(pageIndex: Int) -> PageChildViewController
+ {
+ let storyboard: UIStoryboard = UIStoryboard(name:"Main", bundle:Bundle.main)
+ let child: PageChildViewController = storyboard.instantiateViewController(withIdentifier: "PageChildViewController") as! PageChildViewController
+ child.pageIndex = pageIndex
+ return child
}
override func viewDidLoad() {
@@ -101,10 +99,9 @@ class PageChildViewController: UIViewController {
self.view.backgroundColor = UIColor(red: r, green: g, blue: b, alpha: 1)
// label
- let label: UILabel = UILabel(frame: view.bounds)
- label.text = "page:\(pageIndex)"
- label.textAlignment = .center
-
- view.addSubview(label)
+ pageIndexLabel.text = "page:\(pageIndex)"
+ redValueLabel.text = "R:\(r)"
+ greenValueLabel.text = "G:\(g)"
+ blueValueLabel.text = "B:\(b)"
}
}
--- a/PracticeApp/AppDelegate.swift
+++ b/PracticeApp/AppDelegate.swift
@@ -16,23 +16,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
-
- let tabc: UITabBarController = window?.rootViewController as! UITabBarController
- var newVcs: [UIViewController] = []
-
- // tab1: navigation controller
- for vc in tabc.viewControllers! {
- if vc is UINavigationController {
- newVcs.append(vc)
- }
- }
-
- // tab2: pageview controller
- let pageVc: PageViewController = PageViewController()
- pageVc.tabBarItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 1)
- newVcs.append(pageVc)
-
- tabc.setViewControllers(newVcs, animated: true)
return true
}
3.4. Storyboardの追加修正
PageViewControllerがUIPageViewControllerを継承したことにより、Storyboardのclass設定を追随できるようになっているはず
3.5. 起動
4. まとめ
最終的にPageViewControllerは下記のようになる
import UIKit
class PageViewController:
UIPageViewController,
UIPageViewControllerDelegate,
UIPageViewControllerDataSource
{
var pages: [PageChildViewController] = [
PageChildViewController.getInstance(pageIndex: 0),
]
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
dataSource = self
setViewControllers([self.pages[0]],
direction: .forward,
animated: false,
completion: nil)
}
// UIPageViewDataSource
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let child: PageChildViewController = viewController as! PageChildViewController
let beforePageIndex: Int = child.pageIndex
if 0 == beforePageIndex {
return nil
}
return getPage(pageIndex: beforePageIndex - 1)
}
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let child: PageChildViewController = viewController as! PageChildViewController
let afterPageIndex: Int = child.pageIndex
return getPage(pageIndex: afterPageIndex + 1)
}
// private
func getPage(pageIndex: Int) -> PageChildViewController?
{
if pageIndex < 0 {
return nil
}
while self.pages.count <= pageIndex {
let vc = PageChildViewController.getInstance(pageIndex: self.pages.count)
self.pages.append(vc)
}
return self.pages[pageIndex]
}
}
class PageChildViewController: UIViewController {
@IBOutlet weak var pageIndexLabel: UILabel!
@IBOutlet weak var redValueLabel: UILabel!
@IBOutlet weak var greenValueLabel: UILabel!
@IBOutlet weak var blueValueLabel: UILabel!
var pageIndex: Int = 0
static func getInstance(pageIndex: Int) -> PageChildViewController
{
let storyboard: UIStoryboard = UIStoryboard(name:"Main", bundle:Bundle.main)
let child: PageChildViewController = storyboard.instantiateViewController(withIdentifier: "PageChildViewController") as! PageChildViewController
child.pageIndex = pageIndex
return child
}
override func viewDidLoad() {
super.viewDidLoad()
// background color
var r = 0.1 * CGFloat(self.pageIndex)
while 1 < r {
r -= 1
}
var g = 0.2 * CGFloat(self.pageIndex)
while 1 < g {
g -= 1
}
var b = 0.3 * CGFloat(self.pageIndex)
while 1 < b {
b -= 1
}
self.view.backgroundColor = UIColor(red: r, green: g, blue: b, alpha: 1)
// label
pageIndexLabel.text = "page:\(pageIndex)"
redValueLabel.text = "R:\(r)"
greenValueLabel.text = "G:\(g)"
blueValueLabel.text = "B:\(b)"
}
}
まとめになっていないが特に気にしない。