tvOS Advent Calendar 2017 13日目の記事です。
今回はRxSwiftを使ってUITabBarController
とUISplitViewController
のメニューの開け閉めを実装してみます。
2つともInterface Builder上で簡単に作成できる標準のUIコンポーネントで、これらはiOSと同じくコンテナビューコントローラの仕組みをベースに作られています。
まずはUITabBarController
とUISplitViewController
の開け閉めをプログラムでやるにはどういう処理を書いたらいいのか確認します。どちらも素直なAPIはないのでちょっとしたハックが必要でした。
タブバーの開け閉め
タブバーについてはStackOverflowに断片的に載っていますがそのままでは動かず、結果こういう形に落ち着きました。
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でも似たような感じになるようですね。
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 }
}
}
開け閉めする
あとは開け閉めしたいときに上の実装を呼んであげればいいです。
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
タイマーで自動ドアみたいにしてみました。
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)
}
}
やってることに比べると、だいぶコードがスッキリしてると思います。
まとめ
タブバーとSplitViewのメニュー自動開け閉めをRxSwiftとRxTVで実装してみました。RxTVは他にもrx.didUpdateFocus
やrx.didUpdateFocus(match:)
といった便利APIを提供しているのでよかったら使ってみてください。
tvOS Advent Calendar 2017 明日は @m_yukio による「tvOS再入門」です!