1. 要实现以下这些效果都非常简单
2. 废话不多说,先看看实现效果
3. 下面告诉你我为什么说实现这些效果非常简单
比如说要实现蚂蚁森林的导航栏效果(有以下几个需求):
- 刚进入导航栏透明、两边按钮和文字都是白色、状态栏也是白色
- 向上滚动后导航栏背景由透明逐渐变成白色
- 当超过某一点后,标题变成黑色、状态栏变成黑色、两边按钮变成蓝色
实现步骤:
3.1. 实现刚进入导航栏透明、两边按钮和文字都是白色、状态栏也是白色
override func viewDidLoad()
{
super.viewDidLoad()
// 设置导航栏颜色为白色
navBarBarTintColor = .white
// 设置刚进入页面时透明度为0
navBarBackgroundAlpha = 0
}
3.2. 实现剩下两个需求
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let offsetY = scrollView.contentOffset.y
if (offsetY > NAVBAR_COLORCHANGE_POINT)
{
let alpha = (offsetY - NAVBAR_COLORCHANGE_POINT) / CGFloat(kNavBarBottom)
// 向上滚动后导航栏背景由透明逐渐变成白色
navBarBackgroundAlpha = alpha
if (alpha > 0.5) {
// 当超过某一点后,两边按钮变成蓝色
navBarTintColor = UIColor(red: 0, green: 0.478431, blue: 1, alpha: 1.0)
// 标题变成黑色
navBarTitleColor = .black
// 状态栏变成黑色
statusBarStyle = .default
} else {
// 当没有超过某点,上面属性还原
navBarTintColor = .white
navBarTitleColor = .white
statusBarStyle = .lightContent
}
}
else
{
navBarBackgroundAlpha = 0
navBarTintColor = .white
navBarTitleColor = .white
statusBarStyle = .lightContent
}
}
3.3. 发现没有,改变相关属性只要一句代码就完全搞定了!!!
// 一行代码搞定导航栏颜色
navBarBarTintColor = .white
// 一行代码搞定导航栏透明度
navBarBackgroundAlpha = alpha
// 一行代码搞定导航栏两边按钮颜色
navBarTintColor = UIColor(red: 0, green: 0.478431, blue: 1, alpha: 1.0)
// 一行代码搞定导航栏上标题颜色
navBarTitleColor = .black
// 一行代码搞定状态栏是 default 还是 lightContent
statusBarStyle = .default
3.4. 说了这么多,看看几句代码能否实现我们需要的效果吧
3.5. 有人可能会问:这只是在一个界面里面,但是涉及到push、pop、右滑手势怎么办呢?
答:没关系,我已经给你处理好了,你不用写一句代码!!!那么看看效果吧
4. 好了,说了这么多接下来看看如何实现的吧
4.1 实现导航栏透明渐变就很简单了,网上一找一大堆,大部分都是通过加一层的方法来实现(在这里就是加一个view到navigationBar上)
// set navigationBar barTintColor
fileprivate func wr_setBackgroundColor(color:UIColor)
{
if (backgroundView == nil)
{
// add a image(nil color) to _UIBarBackground make it clear
setBackgroundImage(UIImage(), for: .default)
backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: Int(bounds.width), height: 64))
// _UIBarBackground is first subView for navigationBar
subviews.first?.insertSubview(backgroundView ?? UIView(), at: 0)
}
backgroundView?.backgroundColor = color
}
// set _UIBarBackground alpha (_UIBarBackground subviews alpha <= _UIBarBackground alpha)
fileprivate func wr_setBackgroundAlpha(alpha:CGFloat)
{
let barBackgroundView = subviews[0]
barBackgroundView.alpha = alpha
}
4.2 你以为就这样结束了吗,来看看下面的问题,当由透明的导航栏右滑到不透明的导航栏看看会出现什么情况?
4.3 处理右滑返回手势问题
我们都知道,导航栏是属于导航控制器的,一个导航栏不可能出现两个颜色,那么右滑突兀怎么解决呢?两个方法,一个方法是自定义导航栏(后面会说),另一个方法是改变导航栏颜色,我们假设当前控制器为fromVC,返回的控制器为toVC,如果可以实现从 fromVC 右滑到 toVC 导航栏颜色渐变那么问题就解决了!但是导航栏只有一个颜色啊~~~怎么办?
同样,可以通过加一层的方法来解决。我们可以记录一下 fromVC消失前对应的导航栏颜色 和 toVC 当前的导航栏颜色,然后根据右滑进度percentComplete,来计算渐变色,这样问题就解决了!
记录ViewController对应导航栏的颜色和透明度
// navigationBar barTintColor
var navBarBarTintColor: UIColor {
get {
guard let barTintColor = objc_getAssociatedObject(self, &AssociatedKeys.navBarBarTintColor) as? UIColor else {
return UIColor.defaultNavBarBarTintColor
}
return barTintColor
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.navBarBarTintColor, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
if customNavBar.isKind(of: UINavigationBar.self) {
let navBar = customNavBar as! UINavigationBar
navBar.wr_setBackgroundColor(color: newValue)
}
else
{
if pushToCurrentVCFinished == true && pushToNextVCFinished == false {
navigationController?.setNeedsNavigationBarUpdate(barTintColor: newValue)
}
}
}
}
// navigationBar _UIBarBackground alpha
var navBarBackgroundAlpha:CGFloat {
get {
guard let barBackgroundAlpha = objc_getAssociatedObject(self, &AssociatedKeys.navBarBackgroundAlpha) as? CGFloat else {
return 1.0
}
return barBackgroundAlpha
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.navBarBackgroundAlpha, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
if customNavBar.isKind(of: UINavigationBar.self) {
let navBar = customNavBar as! UINavigationBar
navBar.wr_setBackgroundAlpha(alpha: newValue)
}
else
{
if pushToCurrentVCFinished == true && pushToNextVCFinished == false {
navigationController?.setNeedsNavigationBarUpdate(barBackgroundAlpha: newValue)
}
}
}
}
交换系统方法 _updateInteractiveTransition(监控右滑返回手势的进度)
// swizzling system method: _updateInteractiveTransition
func wr_updateInteractiveTransition(_ percentComplete: CGFloat)
{
guard let topViewController = topViewController,
let coordinator = topViewController.transitionCoordinator else {
wr_updateInteractiveTransition(percentComplete)
return
}
let fromVC = coordinator.viewController(forKey: .from)
let toVC = coordinator.viewController(forKey: .to)
updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: percentComplete)
wr_updateInteractiveTransition(percentComplete)
}
根据 fromVC 与 toVC 的导航栏颜色 配合 返回手势进度计算渐变色
// Calculate the middle Color with translation percent
class fileprivate func middleColor(fromColor: UIColor, toColor: UIColor, percent: CGFloat) -> UIColor
{
// get current color RGBA
var fromRed: CGFloat = 0
var fromGreen: CGFloat = 0
var fromBlue: CGFloat = 0
var fromAlpha: CGFloat = 0
fromColor.getRed(&fromRed, green: &fromGreen, blue: &fromBlue, alpha: &fromAlpha)
// get to color RGBA
var toRed: CGFloat = 0
var toGreen: CGFloat = 0
var toBlue: CGFloat = 0
var toAlpha: CGFloat = 0
toColor.getRed(&toRed, green: &toGreen, blue: &toBlue, alpha: &toAlpha)
// calculate middle color RGBA
let newRed = fromRed + (toRed - fromRed) * percent
let newGreen = fromGreen + (toGreen - fromGreen) * percent
let newBlue = fromBlue + (toBlue - fromBlue) * percent
let newAlpha = fromAlpha + (toAlpha - fromAlpha) * percent
return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: newAlpha)
}
改变导航栏颜色和透明度
fileprivate func updateNavigationBar(fromVC: UIViewController?, toVC: UIViewController?, progress: CGFloat)
{
// change navBarBarTintColor
let fromBarTintColor = fromVC?.navBarBarTintColor ?? .defaultNavBarBarTintColor
let toBarTintColor = toVC?.navBarBarTintColor ?? .defaultNavBarBarTintColor
let newBarTintColor = UIColor.middleColor(fromColor: fromBarTintColor, toColor: toBarTintColor, percent: progress)
setNeedsNavigationBarUpdate(barTintColor: newBarTintColor)
// change navBarTintColor
let fromTintColor = fromVC?.navBarTintColor ?? .defaultNavBarTintColor
let toTintColor = toVC?.navBarTintColor ?? .defaultNavBarTintColor
let newTintColor = UIColor.middleColor(fromColor: fromTintColor, toColor: toTintColor, percent: progress)
setNeedsNavigationBarUpdate(tintColor: newTintColor)
// change navBarTitleColor
let fromTitleColor = fromVC?.navBarTitleColor ?? .defaultNavBarTitleColor
let toTitleColor = toVC?.navBarTitleColor ?? .defaultNavBarTitleColor
let newTitleColor = UIColor.middleColor(fromColor: fromTitleColor, toColor: toTitleColor, percent: progress)
setNeedsNavigationBarUpdate(titleColor: newTitleColor)
// change navBar _UIBarBackground alpha
let fromBarBackgroundAlpha = fromVC?.navBarBackgroundAlpha ?? UIColor.defaultBackgroundAlpha
let toBarBackgroundAlpha = toVC?.navBarBackgroundAlpha ?? UIColor.defaultBackgroundAlpha
let newBarBackgroundAlpha = UIColor.middleAlpha(fromAlpha: fromBarBackgroundAlpha, toAlpha: toBarBackgroundAlpha, percent: progress)
setNeedsNavigationBarUpdate(barBackgroundAlpha: newBarBackgroundAlpha)
}
好了!来看看处理后的效果吧,是不是好多了呢~
4.4 别高兴的太早,还有其他问题。在右滑返回手势的过程中,导航栏颜色和透明度会根据手势变化而变化。但是一旦松手,系统会自动完成或取消返回操作。导致透明度停留在最后的那个状态。
4.5 咱们来处理右滑返回手势中断的问题吧~
通过遵守UINavigationBarDelegate协议,实现
optional public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool // same as push methods 方法来监听右滑返回手势中断的情况
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool
{
if let topVC = topViewController,
let coor = topVC.transitionCoordinator, coor.initiallyInteractive {
if #available(iOS 10.0, *) {
coor.notifyWhenInteractionChanges({ (context) in
self.dealInteractionChanges(context)
})
} else {
coor.notifyWhenInteractionEnds({ (context) in
self.dealInteractionChanges(context)
})
}
return true
}
let itemCount = navigationBar.items?.count ?? 0
let n = viewControllers.count >= itemCount ? 2 : 1
let popToVC = viewControllers[viewControllers.count - n]
popToViewController(popToVC, animated: true)
return true
}
// deal the gesture of return break off
private func dealInteractionChanges(_ context: UIViewControllerTransitionCoordinatorContext)
{
let animations: (UITransitionContextViewControllerKey) -> () = {
let curColor = context.viewController(forKey: $0)?.navBarBarTintColor ?? UIColor.defaultNavBarBarTintColor
let curAlpha = context.viewController(forKey: $0)?.navBarBackgroundAlpha ?? UIColor.defaultBackgroundAlpha
self.setNeedsNavigationBarUpdate(barTintColor: curColor)
self.setNeedsNavigationBarUpdate(barBackgroundAlpha: curAlpha)
}
// after that, cancel the gesture of return
if context.isCancelled
{
let cancelDuration: TimeInterval = context.transitionDuration * Double(context.percentComplete)
UIView.animate(withDuration: cancelDuration) {
animations(.from)
}
}
else
{
// after that, finish the gesture of return
let finishDuration: TimeInterval = context.transitionDuration * Double(1 - context.percentComplete)
UIView.animate(withDuration: finishDuration) {
animations(.to)
}
}
}
处理后:
4.5 同样,push和pop也要处理一下 ~
但是push和pop我们拿不到进度怎么办呢?处理办法是,通过交换方法,自己实现给push和pop添加进度。
// MARK: swizzling push
struct pushProperties {
fileprivate static let pushDuration = 0.13
fileprivate static var displayCount = 0
fileprivate static var pushProgress:CGFloat {
let all:CGFloat = CGFloat(60.0 * pushDuration)
let current = min(all, CGFloat(displayCount))
return current / all
}
}
// swizzling system method: pushViewController
func wr_pushViewController(_ viewController: UIViewController, animated: Bool)
{
var displayLink:CADisplayLink? = CADisplayLink(target: self, selector: #selector(pushNeedDisplay))
displayLink?.add(to: RunLoop.main, forMode: .defaultRunLoopMode)
CATransaction.setCompletionBlock {
displayLink?.invalidate()
displayLink = nil
pushProperties.displayCount = 0
viewController.pushToCurrentVCFinished = true
};
CATransaction.setAnimationDuration(pushProperties.pushDuration)
CATransaction.begin()
wr_pushViewController(viewController, animated: animated)
CATransaction.commit()
}
计算push进度,并且根据进度更新导航栏颜色和透明度
// change navigationBar barTintColor smooth before push to current VC finished or before pop to current VC finished
func pushNeedDisplay()
{
guard let topViewController = topViewController,
let coordinator = topViewController.transitionCoordinator else {
return
}
pushProperties.displayCount += 1
let pushProgress = pushProperties.pushProgress
// print("第\(pushProperties.displayCount)次push的进度:\(pushProgress)")
let fromVC = coordinator.viewController(forKey: .from)
let toVC = coordinator.viewController(forKey: .to)
updateNavigationBar(fromVC: fromVC, toVC: toVC, progress: pushProgress)
}
pop的设置方法也一样,具体请查看代码 WRNavigationBar_swift
4.6 以上都是改变导航栏的颜色和透明度,同样改变导航栏的按钮颜色和标题颜色,以及状态栏状态都和改变颜色一样,每个ViewController记录一下。需要改变的时候,ViewController 改变一下属性就ok了,非常方便!
那么接下来看一下其他demo的动态效果图吧~~~
5. 好了,来说说前面提过的自定义导航栏吧
5.1 至于怎么自定义导航栏我就不说了,来说说如果是自定义导航栏,那怎么才能像之前一样一句代码改变导航栏属性。
经过封装,自定义导航栏只需要多写一行代码!!!
// 自定义导航栏必须设置这个属性!!!!!!
customNavBar = navBar
如果把这行代码放在基类控制器中,那么其他所有继承基类控制器都可以一句代码修改导航栏属性~~~
看看自定义导航栏的效果吧,是不是也很棒
6. 最后看一下移动导航栏的效果
实现代码
/// 设置导航栏在垂直方向上平移多少距离
func wr_setTranslationY(translationY:CGFloat)
{
transform = CGAffineTransform.init(translationX: 0, y: translationY)
}
到这里就结束🌶,具体代码请前往:
https://github.com/wangrui460/WRNavigationBar
https://github.com/wangrui460/WRNavigationBar_swift
参考资料:
http://www.jianshu.com/p/640b64faea9a
http://www.jianshu.com/p/540a7e6f7b40
https://github.com/jawadasif/JNAPushPopCompletionBlock
http://www.jianshu.com/p/e3ca1b7b6cec
http://www.jianshu.com/p/454b06590cf1
欢迎关注我的微博:wangrui460