[tvOS] タブバーとSplitViewのメニュー開け閉め with RxTV


tvOS Advent Calendar 2017 13日目の記事です。

今回はRxSwiftを使って`UITabBarController`と`UISplitViewController`のメニューの開け閉めを実装してみます。

2つともInterface Builder上で簡単に作成できる標準のUIコンポーネントで、これらはiOSと同じくコンテナビューコントローラの仕組みをベースに作られています。
![tabbar-split.png](https://qiita-image-store.s3.amazonaws.com/0/35008/4c68064f-51bf-b0b4-a75b-943ac51ed994.png "tabbar-split.png")

まずは`UITabBarController`と`UISplitViewController`の開け閉めをプログラムでやるにはどういう処理を書いたらいいのか確認します。どちらも素直なAPIはないのでちょっとしたハックが必要でした。

# タブバーの開け閉め
タブバーについてはStackOverflowに断片的に載っていますがそのままでは動かず、結果こういう形に落ち着きました。

```swift
extension UITabBarController {

   func showTabBar() {

        self.tabBar.alpha = 1
        self.tabBar.isHidden = false

        UIView.animate(
            withDuration: 0.5,
            delay: 0,
            usingSpringWithDamping: 1,
            initialSpringVelocity: 1,
            options: UIViewAnimationOptions.curveEaseOut,
            animations:
        {
            self.base.tabBar.frame.origin.y = 0

        }, completion: { _ in

            self.setNeedsFocusUpdate()
            self.updateFocusIfNeeded()

        })
    }

    func hideTabBar() {

        UIView.animate(
            withDuration: 0.5,
            delay: 0,
            usingSpringWithDamping: 1,
            initialSpringVelocity: 1,
            options: UIViewAnimationOptions.curveEaseOut,
            animations:
        {
            self.base.tabBar.frame.origin.y = -self.base.tabBar.bounds.height

        }, completion: { _ in

            self.tabBar.isHidden = true
            self.tabBar.alpha = 0

            if let focused = UIScreen.main.focusedView,
                focused.isDescendant(of: self.base.tabBar) {

                self.setNeedsFocusUpdate()
                self.updateFocusIfNeeded()
            }
        })
    }
}
```

# SplitViewの開け閉め

こちらはiOSでも似たような感じになるようですね。

```swift
extension UISplitViewController {

    func hideMasterViewIfNeeded() {
        if !_isMasterViewHidden { toggleMasterView() }
    }

    func showMasterViewIfNeeded() {
        if _isMasterViewHidden { toggleMasterView() }
    }

    private func toggleMasterView() {
        let barButtonItem = displayModeButtonItem
        UIApplication.shared.sendAction(barButtonItem.action!,
        								to: barButtonItem.target,
        								from: nil,
        								for: nil)
    }

    private var _isMasterViewHidden: Bool {
        return view.subviews.contains { $0.frame.origin.x < 0 }
    }

}
```

# 開け閉めする
あとは開け閉めしたいときに上の実装を呼んであげればいいです。

```swift
    isTabBarHidden
    	.throttle(latest: true, scheduler: MainScheduler.instance)
    	.observeOn(MainScheduler.instance)
    	.subscribe(onNext: { [weak self] isHidden in
    	    if isHidden {
	    		self?.tabBarController?.hideTabBar()
	    	} else {
	    		self?.tabBarController?.showTabBar()
	    	}
    	})
    	.disposed(by: rx.disposeBag)
```

# RxTV
上のコードのままでもいいですが、RxSwiftの世界では基本的にはbindだけで済ませられるとコードがスッキリするので、まとめましょう。
RxTVを使うと、上のようなコードは一切書かずにBinderとして実装してある`rx.isMasterViewHidden`と`rx.isTabBarHidden`のプロパティにそれぞれbindするだけで済みます。
https://github.com/toshi0383/RxTV

タイマーで自動ドアみたいにしてみました。

```swift
import RxSwift
import RxTV
import UIKit

class SplitViewController: UISplitViewController {

    private var disposeBag = DisposeBag()

    override func viewDidLoad() {

        super.viewDidLoad()
        
        let main = MainScheduler.instance

        Observable<Int>.timer(1.0, period: 1.0, scheduler: main)
            .map { $0 % 2 == 0 }
            .bind(to: rx.isMasterViewHidden)
            .disposed(by: disposeBag)

        Observable<Int>.timer(1.2, period: 1.2, scheduler: main)
            .map { $0 % 2 == 0 }
            .bind(to: tabBarController!.rx.isTabBarHidden)
            .disposed(by: disposeBag)

    }

}
```

![Nov-26-2017 22-15-06.gif](https://qiita-image-store.s3.amazonaws.com/0/35008/db06f459-d560-d361-7102-6c3e11213edd.gif "Nov-26-2017 22-15-06.gif")

やってることに比べると、だいぶコードがスッキリしてると思います。

# まとめ
タブバーとSplitViewのメニュー自動開け閉めをRxSwiftとRxTVで実装してみました。RxTVは他にも`rx.didUpdateFocus`や`rx.didUpdateFocus(match:)`といった便利APIを提供しているのでよかったら使ってみてください。

tvOS Advent Calendar 2017 明日は @m_yukio による「tvOS再入門」です！