关于为单个视图控制器添加UIToolbar并通过标准UIKit动画控制其显隐的技术问询
关于为单个视图控制器添加UIToolbar并通过标准UIKit动画控制其显隐的技术问询
嘿,我来帮你捋捋这个问题。首先得明确一点:navigationController?.setToolbarHidden(_:animated:)这个方法只针对导航控制器自身管理的UIToolbar,所以你在单个VC里单独添加的Toolbar肯定没法直接用这个API,这就是你遇到问题的根源。
接下来分两种情况给你实用的解决方案:
一、给单独的UIToolbar实现系统风格的显隐动画
如果你只是想让这个单独的Toolbar拥有和系统导航栏Toolbar一致的显隐动画效果,完全不需要手动硬写复杂逻辑,UIKit已经给我们提供了现成的动画参数可以复用:
- 直接模拟系统动画参数
系统的Toolbar显隐动画用的是特定的时长和曲线,你可以直接在UIView动画里套用这些参数:
// 隐藏Toolbar UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut, .beginFromCurrentState], animations: { self.toolbar.alpha = 0 self.toolbar.transform = CGAffineTransform(translationX: 0, y: self.toolbar.bounds.height) }, completion: { _ in self.toolbar.isHidden = true }) // 显示Toolbar self.toolbar.isHidden = false UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut, .beginFromCurrentState], animations: { self.toolbar.alpha = 1 self.toolbar.transform = .identity }, completion: nil)
这里的0.3秒是系统默认的栏动画时长,.curveEaseInOut是系统用的动画曲线,.beginFromCurrentState能保证动画中途被打断时也能平滑过渡,和系统行为完全一致。
- 利用UINavigationController的动画参数(更精准)
如果你想完全和当前导航控制器的动画参数同步,可以直接获取导航控制器的动画配置:
let duration = navigationController?.hideShowBarDuration ?? 0.3 let curve = UIView.AnimationOptions(rawValue: UInt((navigationController?.hideShowBarAnimationCurve.rawValue)!)) // 隐藏动画示例 UIView.animate(withDuration: duration, delay: 0, options: curve.union(.beginFromCurrentState), animations: { self.toolbar.alpha = 0 self.toolbar.transform = CGAffineTransform(translationX: 0, y: self.toolbar.bounds.height) }, completion: { _ in self.toolbar.isHidden = true })
这样就能保证你的Toolbar动画和系统导航栏的动画完全同节奏。
二、让普通UIView拥有类似导航栏的滚动驱动显隐效果
如果你想让一个普通UIView也能像导航栏那样,随着用户滚动UIScrollView(比如UITableView、UICollectionView)自动滑入滑出,可以通过UIScrollViewDelegate来实现:
- 监听滚动偏移,计算动画进度
首先在VC里实现UIScrollViewDelegate的scrollViewDidScroll(_:)方法:
var lastContentOffset: CGFloat = 0 func scrollViewDidScroll(_ scrollView: UIScrollView) { let currentOffset = scrollView.contentOffset.y let offsetDiff = currentOffset - lastContentOffset // 根据滚动方向计算视图的目标位置 var targetY = customView.frame.origin.y if offsetDiff > 0 { // 向下滚动,隐藏视图 targetY = min(view.bounds.height, targetY + offsetDiff) } else { // 向上滚动,显示视图 targetY = max(view.bounds.height - customView.bounds.height, targetY + offsetDiff) } // 应用系统风格的动画 UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseOut, .beginFromCurrentState], animations: { self.customView.frame.origin.y = targetY }, completion: nil) lastContentOffset = currentOffset }
这里的customView就是你要控制的普通UIView,通过监听滚动方向来调整它的Y坐标,实现滑入滑出的效果。
- 关于UIBarPositioningDelegate的说明
你提到的UIBarPositioningDelegate其实主要是用来告诉UIKit你的Bar(比如UIToolbar)在屏幕上的位置(比如是否附着在顶部/底部),它并不能直接驱动动画,但如果你是用UIToolbar的话,设置它的delegate为当前VC并实现barPosition(for:)方法,可以让Toolbar的布局更贴合系统规范,比如避免被安全区域遮挡:
extension YourViewController: UIBarPositioningDelegate { func barPosition(for bar: UIBarPositioning) -> UIBarPosition { return .bottomAttached // 如果你的Toolbar在底部 } }
总结一下
- 单独的UIToolbar没法用导航控制器的
setToolbarHidden,但可以通过模拟系统动画参数实现一致的显隐效果 - 普通UIView可以通过监听滚动视图的偏移来实现类似导航栏的滚动驱动动画
UIBarPositioningDelegate主要用于Bar的位置布局,不是动画驱动,但能优化Toolbar的系统兼容性
内容来源于stack exchange
相关产品推荐
相关产品推荐

