如何通过Storyboard添加全局底部播放视图(排除详情ViewController)
实现全局底部播放栏(Storyboard + ViewController 方案)
我刚好做过类似的需求,用Storyboard设计播放栏再结合自定义导航控制器,就能完美实现全局显示+排除特定页面的效果,具体步骤如下:
1. 创建播放栏专用 ViewController 并在 Storyboard 布局
- 新建
NowPlayingBarViewController继承自UIViewController - 打开 Storyboard(可以用Main.storyboard或者单独新建一个),拖入一个ViewController,设置其Class为
NowPlayingBarViewController - 在Size Inspector里把这个VC的Size改成Freeform,调整高度到60pt左右(和苹果音乐播放栏高度匹配),宽度设为屏幕宽度
- 设计UI:添加专辑封面ImageView、歌曲名Label、歌手Label、播放暂停按钮等,调整布局和样式(比如半透明背景、圆角)
- 给这个VC设置Storyboard ID(比如
NowPlayingBarVC),方便后续代码初始化
2. 自定义导航控制器承载播放栏
要实现全局显示且排除特定页面,用自定义导航控制器管理是最灵活的方案:
- 新建
CustomNavigationController继承自UINavigationController - 在这个类里初始化播放栏VC并添加为子VC,同时监听导航栈变化来控制显示隐藏:
import UIKit class CustomNavigationController: UINavigationController { private var nowPlayingBarVC: NowPlayingBarViewController? override func viewDidLoad() { super.viewDidLoad() setupNowPlayingBar() // 设置代理监听页面切换 delegate = self } private func setupNowPlayingBar() { // 从Storyboard初始化播放栏VC let storyboard = UIStoryboard(name: "Main", bundle: nil) guard let barVC = storyboard.instantiateViewController(withIdentifier: "NowPlayingBarVC") as? NowPlayingBarViewController else { return } nowPlayingBarVC = barVC // 添加为子VC并布局到底部 addChild(barVC) view.addSubview(barVC.view) barVC.didMove(toParent: self) // Auto Layout 约束 barVC.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ barVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), barVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), barVC.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), barVC.view.heightAnchor.constraint(equalToConstant: 60) ]) } } // 监听导航页面切换,控制播放栏显示隐藏 extension CustomNavigationController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { // 判断当前页面是否是需要排除的详情页(替换成你的详情VC类名) let shouldHideBar = viewController is DetailViewController nowPlayingBarVC?.view.isHidden = shouldHideBar } }
3. 配置项目使用自定义导航控制器
- 在Storyboard里,把原来的
UINavigationController替换成CustomNavigationController - 确保所有需要显示播放栏的页面都在这个导航控制器的栈中,详情页也通过这个导航控制器push/pop,这样切换时会自动触发播放栏的显示/隐藏
4. 同步播放状态与UI
要让播放栏实时更新歌曲信息和播放状态,推荐用通知中心实现:
- 在
NowPlayingBarViewController里监听播放状态变化的通知:
override func viewDidLoad() { super.viewDidLoad() // 监听播放更新通知 NotificationCenter.default.addObserver( self, selector: #selector(updateNowPlayingUI), name: NSNotification.Name("NowPlayingStatusUpdated"), object: nil ) } @objc private func updateNowPlayingUI(_ notification: Notification) { // 从通知userInfo中获取数据更新UI if let song = notification.userInfo?["currentSong"] as? SongModel { songNameLabel.text = song.title artistLabel.text = song.artist albumCoverImageView.image = song.coverImage } let isPlaying = notification.userInfo?["isPlaying"] as? Bool ?? false playPauseButton.setImage( isPlaying ? UIImage(systemName: "pause.fill") : UIImage(systemName: "play.fill"), for: .normal ) } // 记得移除监听 deinit { NotificationCenter.default.removeObserver(self) }
- 在你的播放管理器(比如
AudioPlayerManager)中,当歌曲切换或播放状态改变时发送通知:
func updatePlayingStatus(song: SongModel, isPlaying: Bool) { NotificationCenter.default.post( name: NSNotification.Name("NowPlayingStatusUpdated"), object: nil, userInfo: [ "currentSong": song, "isPlaying": isPlaying ] ) }
额外注意点
- 如果你的App用了TabBarController,需要让TabBar的每个子控制器都使用这个自定义导航控制器,保证所有页面都能统一控制播放栏
- 播放栏的点击事件(比如跳转到播放详情页)可以在
NowPlayingBarViewController里添加按钮点击方法,通过导航控制器push对应的页面 - 可以给播放栏添加毛玻璃效果:给播放栏的View添加
UIVisualEffectView作为子View,设置为系统的模糊效果,更贴近苹果音乐的风格
内容的提问来源于stack exchange,提问作者Jacob Cavin
相关产品推荐
相关产品推荐

