AppleMusicのようなTabBarにDockされたツールバーを構築する
AppleMusicのiOSアプリには、TabBarの上に再生中の音楽を示したツールバーが表示されます。
今回はタブを切り替えたり、UINavigationControllerでpush遷移をしても単一のインスタンスを使用して表示ができるような実装をしてみます。
完成イメージ

実装概要
UITabBarControllerを継承したTabBarController側にPlayer用のUIViewをレイアウトし、公開させておきます。
UITabBarControllerに埋め込まれたUIViewControllerからはUITabBarControllerが取得できるので、それを経由してPlayerインスタンスにアクセスし、
レイアウトに使用することができます。
実装を簡易化するため、TabBarVisible protocolを作成し、protocol extensionでplayerを取得できるようにします。
注意点としてはtabBarControllerはviewDidLoad時点では取得することができないので、updateViewConstraints等でレイアウトをする必要があります。
実装
import UIKit
final class TabBarController: UITabBarController {
enum Const {
static let playerHeight: CGFloat = 64
}
let player: UILabel = {
let label = UILabel()
label.text = "This is Music Player"
label.font = .boldSystemFont(ofSize: 18)
label.backgroundColor = UIColor.purple
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
player.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(player)
NSLayoutConstraint.activate([
player.heightAnchor.constraint(equalToConstant: Const.playerHeight),
player.leftAnchor.constraint(equalTo: view.leftAnchor),
player.rightAnchor.constraint(equalTo: view.rightAnchor),
player.bottomAnchor.constraint(equalTo: tabBar.topAnchor)
])
viewControllers = [
TopViewController(title: "Page1", color: .green),
TopViewController(title: "Page2", color: .yellow)
]
}
}
import UIKit
protocol TabBarVisible {}
extension TabBarVisible where Self: UIViewController {
var player: UIView {
guard let tabBarController = tabBarController as? TabBarController else {
fatalError("Must to be embbeded in UITabBarController")
}
return tabBarController.player
}
}
import UIKit
final class TopViewController: UIViewController, TabBarVisible {
private let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.minimumScaleFactor = 0.1
label.adjustsFontSizeToFitWidth = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
init(title: String, color: UIColor) {
super.init(nibName: nil, bundle: nil)
self.title = title
label.text = Array(repeating: title, count: 100).joined()
view.backgroundColor = color
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(label)
}
override func updateViewConstraints() {
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.topAnchor),
label.rightAnchor.constraint(equalTo: view.rightAnchor),
label.leftAnchor.constraint(equalTo: view.leftAnchor),
label.bottomAnchor.constraint(equalTo: player.topAnchor)
])
super.updateViewConstraints()
}
}