ここ1,2年でよく見るようになったUIパターンかなと思います。objcで書いてあったものを、練習がてらswiftで書きなおしてみました。
(追記: 2015/04/09)
なんと!UIPageViewControllerなるものがありました...
長所/短所あるかもしれませんが、こちらで実装するのが良いのかもしれません。
http://qiita.com/yd_niku/items/b9f10bf5071971a1d8da
作ったもの
AndroidでいうViewPager的なUIのiOSバージョンです。
ソース
構成
大体4つのパーツから成り立っています
- MenuView... 各ページのタイトルを表示したり、UIPageControlを保持しているview
- ContentScrollView... 各ページのコンテンツとなるViewを貼り付けるUIScrollView
- SimpleViewController... 上記2つのViewを管理するViewController
- Main.storyboard... SimpleViewControllerのViewをレイアウトするStoryboard
制約事項
- 一応横画面にも対応
- デプロイメントターゲットはiOS 7.0
- できるだけシンプルにしてみたので、カスタマイズしやすいのではないかと...(リファクタ大歓迎)
仕組み的な話
メニューの同期部分
- MenuViewとSimpleViewControllerのUIScrollViewDelegateでスクロールイベントを検知し、お互いに通知しあっています。
- MenuViewのタップイベントに関しては、UITapGestureRecognizerを追加しました。
- ページ管理はMenuViewのUIPageControlを共同で利用しています。
autolayout部分
- MenuViewとContentScrollViewのlayoutは、**
Main.storyboard
**に任せます - 各ページのレイアウトは、各ページのStoryboard(ここでは**
Some.Storyboard
**)に任せます - UIScrollViewに貼り付けているViewは **
viewDidLayoutSubviews()
や、layoutSubviews()
**で都度調整しています
ちょっとトリッキー?な部分
各ページのタイトルを貼り付けるUIScrollViewのframeはgifアニメで見ると、濃い灰色になっている部分です。つまり通常はこの濃い灰色部分しかスクロールイベントが発生しないのですが、今回は、MenuView全体でイベントを発生させたかったので、下記のようにhitTestをオーバーライドして、MenuView内であればどこでもスクロールイベントが発生するようにしました。
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
// Check if within own bounds. If YES, pass touch event to scrollView
if self.pointInside(point, withEvent: event) {
return self.scrollView
}
return nil
}
使い方
1. いわゆるモデルになるMenuElem
を用意します。
MenuElem(name: "menu1", className: "SomeViewController", sbName: "Some")
- name... ページのタイトル文字列です
- className... ページのviewを管理するViewControllerのサブクラス名です
- sbName... 上記ViewControllerと対応するStoryboardです
2. MenuElem
を適切な順番に並べます
(配列のindexが若いほうが左から並びます)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let menus: Array<MenuElem> =
[
MenuElem(name: "menu1", className: "SomeViewController", sbName: "Some"),
MenuElem(name: "menu2", className: "SomeViewController", sbName: "Some"),
MenuElem(name: "menu3", className: "SomeViewController", sbName: "Some"),
MenuElem(name: "menu4", className: "SomeViewController", sbName: "Some"),
MenuElem(name: "menu5", className: "SomeViewController", sbName: "Some"),
MenuElem(name: "menu6", className: "SomeViewController", sbName: "Some"),
]
self.menuView.setup(menus, delegate:self)
self.setupContentScrollView(menus)
}
3. 必要に応じて各ViewControllerにパラメータを渡します
(ここでは、暫定的にextensionで渡していますが、きれいな渡し方を募集中です。)
func setupContentScrollView(menus: Array<MenuElem>) {
// http://app.coolors.co/e0acd5-3993dd-29e7cd-6a3e37-c7f0bd
let colorPalette = [0xE0ACD5, 0x3993DD, 0x29E7CD, 0x6A3E37, 0xC7F0BD]
var cnt = 0
for menu in menus {
let sb: UIStoryboard = UIStoryboard(name: menu.storyBoardName(), bundle: nil)
let vc = sb.instantiateInitialViewController() as UIViewController
vc.menu = menu
vc.view.backgroundColor = UIColor(netHex: colorPalette[cnt%colorPalette.count])
self.contentScrollView.addSubview(vc.view)
self.vcs.append(vc)
cnt++
}
}
感想
最初は、UIScrollViewの中身に貼り付けるViewをどうやって再レイアウトさせよう...と思ってVisual Format Languageを使ってやっていたんですが、結局
- UIScrollViewに貼り付けているViewは **
viewDidLayoutSubviews()
や、layoutSubviews()
**で都度調整しています
とふうにできることがわかり、そっちにしました。。ソースからは削ったんですが、だいぶ勉強になったのでそこは別の機械に書いてみようかなと思います。
あと、このサンプルにTableViewとかcollectionViewとか入れて試してみたい。
最後に
改善大歓迎です!