[Swift] AndroidでいうViewPager的な左右にスワイプしてviewを切り替えるサンプルを書いてみた

ここ1,2年でよく見るようになったUIパターンかなと思います。objcで書いてあったものを、練習がてらswiftで書きなおしてみました。

(追記: 2015/04/09)
なんと！UIPageViewControllerなるものがありました...
長所/短所あるかもしれませんが、こちらで実装するのが良いのかもしれません。
http://qiita.com/yd_niku/items/b9f10bf5071971a1d8da

# 作ったもの
AndroidでいうViewPager的なUIのiOSバージョンです。

![gif](https://dl.dropboxusercontent.com/u/91774212/simpleViewPager.gif)

# ソース
https://github.com/mitolog/SimpleViewPager

# 構成
大体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内であればどこでもスクロールイベントが発生するようにしました。

```swift:MenuView.swift
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`を用意します。

```swift
MenuElem(name: "menu1", className: "SomeViewController", sbName: "Some")
```

 - name... ページのタイトル文字列です
 - className... ページのviewを管理するViewControllerのサブクラス名です
 - sbName... 上記ViewControllerと対応するStoryboardです

## 2. `MenuElem`を適切な順番に並べます
(配列のindexが若いほうが左から並びます)

```swift:SimpleViewController.swift
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で渡していますが、きれいな渡し方を募集中です。)

```swift:SimpleViewController.swift
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](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage/VisualFormatLanguage.html)を使ってやっていたんですが、結局


>  - UIScrollViewに貼り付けているViewは **`viewDidLayoutSubviews()`**や、**`layoutSubviews()`**で都度調整しています

とふうにできることがわかり、そっちにしました。。ソースからは削ったんですが、だいぶ勉強になったのでそこは別の機械に書いてみようかなと思います。

あと、このサンプルにTableViewとかcollectionViewとか入れて試してみたい。

# 最後に
改善大歓迎です！

# 参考

[Swiftで既存のクラスに新たにプロパティを追加する方法](http://qiita.com/__moai/items/8904563cfcacd5023145)

[uacaps/PageMenu (今回のサンプルのもっと凄いversion)](https://github.com/uacaps/PageMenu)