UIViewContorllerをUITabBarControllerのように振る舞わせる

自作タブ再び。`UIPageViewController`を使うやつを以前書きました。
[UIPageViewControllerを使って自前のタブ的なものを作る](http://qiita.com/Yaruki00/items/322c7c7467a05b6031e8)

今回は`UIViewController`のみを使います。
成果物: [Yaruki00/YKStretchTabViewController](https://github.com/Yaruki00/YKStretchTabViewController)

# ざっくりと中身説明

## ビューの準備
ページの内容を表示するビューと、タブを表示するビューを用意します。

```swift:lib/YKStretchTabViewController.swift
...
    lazy public var tabView = UIView(frame: CGRect.zero)
    ...
    
    lazy private var containerView = UIView(frame: CGRect.zero)
    ...    

    override public func viewDidLoad() {
        super.viewDidLoad()
        
        // サブビューに追加
        self.view.addSubview(self.containerView)
        self.view.addSubview(self.tabView)
    }
    
    override public func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // サブビューのレイアウト調整
        self.containerView.frame = self.view.frame
        self.tabView.frame = CGRect(x: 0, y: self.view.frame.height - self.tabHeight, width: self.view.frame.width, height: self.tabHeight)
        // タブ
        let tabWidth = self.tabView.frame.width / CGFloat(self.tabInfoList.count)
        var tabX: CGFloat = 0
        for (index, tabInfo) in self.tabInfoList.enumerated() {
            let tab = tabInfo.tab
            tab.frame = CGRect(x: tabX, y: 0, width: ceil(CGFloat(index + 1) * tabWidth) - tabX, height: self.tabView.frame.height)
            tabX = ceil(CGFloat(index + 1) * tabWidth)
        }
    }
...
```

## タブを作る
何処かから渡ってくる`ViewController`のリスト、タブの選択時・非選択時の画像リストからタブを作ります。タブの実態はボタンです。

```swift:lib/YKStretchTabViewController.swift
...
    // タブ情報を渡してタブを作る
    public func setTabs(contentViewControllerList: [UIViewController], tabSelectedImageList: [UIImage], tabNormalImageList: [UIImage]) {
        
        // エラーチェック
        ...
        
        // 一旦初期化
        for subview in self.tabView.subviews {
            subview.removeFromSuperview()
        }
        self.tabInfoList = []
        
        // タブ準備
        for index in 0..<contentViewControllerList.count {
            
            let tab: UIButton = {
                let button = UIButton(frame: CGRect.zero)
                button.tag = index
                button.addTarget(self, action: #selector(YKStretchTabViewController.tabTapped(sender:)), for: .touchUpInside)
                self.tabView.addSubview(button)
                return button
            }()
            
            let tabInfo = TabInfo(vc: contentViewControllerList[index], tab: tab, onImage: tabSelectedImageList[index], offImage: tabNormalImageList[index])
            tabInfo.setOffImage()
            
            tab.setBackgroundImage(tabSelectedImageList[index], for: .highlighted)
            
            self.tabInfoList.append(tabInfo)
        }
        
        // 最初の画面をセット
        if self.selectedTabIndex < self.tabInfoList.count {
            let newVC = self.tabInfoList[self.selectedTabIndex].vc
            self.addChildViewController(newVC)
            self.containerView.addSubview(newVC.view)
            newVC.didMove(toParentViewController: self)
            self.tabInfoList[self.selectedTabIndex].setOnImage()
        }
        else {
            ...
        }
    }
...
```

## 画面を入れ替える
タブが押されたら画面を入れ替えます。`ViewController`と`View`をそれぞれ新しいものにセットし直すことで画面を入れ替えることができます。

```swift:lib/YKStretchTabViewController.swift
...
    // タブが押された
    func tabTapped(sender: UIButton) {
        self.switchContentViewController(index: sender.tag)
    }
    
    // 画面入れ替え
    public func switchContentViewController(index: Int) {
        
        // エラーチェック
        ...
        
        // 前の画面を取り除く
        self.childViewControllers.first!.willMove(toParentViewController: nil)
        self.childViewControllers.first!.view.removeFromSuperview()
        self.childViewControllers.first!.removeFromParentViewController()
        
        // 新しい画面をセット
        let newVC = self.tabInfoList[index].vc
        self.addChildViewController(newVC)
        self.containerView.addSubview(newVC.view)
        newVC.didMove(toParentViewController: self)
        
        // タブを切り替え
        self.tabInfoList[self.selectedTabIndex].setOffImage()
        self.selectedTabIndex = index
        self.tabInfoList[self.selectedTabIndex].setOnImage()
    }
...
```

# 使ってみる

## デフォルト
初期化して、`setTabs(...)`を呼べばokです。

```swift:SampleViewController.swift
...
        let vc = YKStretchTabViewController()
        vc.setTabs(
            contentViewControllerList: [
                {
                    let vc = UIViewController()
                    vc.view.backgroundColor = .red
                    vc.view.frame = self.view.frame
                    let label = UILabel(frame: CGRect(x: 200, y: 200, width: 50, height: 20))
                    label.text = "1"
                    vc.view.addSubview(label)
                    return vc
                }(),
                {
                    let vc = UIViewController()
                    vc.view.backgroundColor = .green
                    vc.view.frame = self.view.frame
                    let label = UILabel(frame: CGRect(x: 200, y: 200, width: 50, height: 20))
                    label.text = "2"
                    vc.view.addSubview(label)
                    return vc
                }(),
                {
                    let vc = UIViewController()
                    vc.view.backgroundColor = .blue
                    vc.view.frame = self.view.frame
                    let label = UILabel(frame: CGRect(x: 200, y: 200, width: 50, height: 20))
                    label.text = "3"
                    vc.view.addSubview(label)
                    return vc
                }(),
            ],
            tabSelectedImageList: [
                UIImage(named: "tab1:3_on")!,
                UIImage(named: "tab2:3_on")!,
                UIImage(named: "tab3:3_on")!,
            ],
            tabNormalImageList: [
                UIImage(named: "tab1:3_off")!,
                UIImage(named: "tab2:3_off")!,
                UIImage(named: "tab3:3_off")!,
            ]
        )
        self.navigationController?.pushViewController(vc, animated: true)
...
```

## オプション
タブの初期選択位置と高さが変えられます。

```swift:SampleViewController.swift
...
        let vc = YKStretchTabViewController()
        vc.selectedTabIndex = 2
        vc.tabHeight = 100
        vc.setTabs(
...
```

# おわりに
今回のコードは、タブ領域全体に背景を置きたいという要求があって書いたものです(標準ではアイコン+テキストしかできない)。
`UIPageViewController`を使うやつに比べると、だいぶシンプルに書けました。
今回やってないですが、`containerView`に`GestureRecognizer`をつければ、スワイプでページ切替もできるんじゃないでしょうか。
`UIViewController`だけでも親子関係を作れるようになっているので、結構色々なものが自作できそうですね。