UINavigationBar简单解析

额外知识

在开始写UINavigationBar之前,了解几个导航栏中用到的知识,将会更有利于理解。

可单独使用

首先需要明确UINavigationBar是可以脱离UINavigationConroller单独作为控件的。只是UINavigationConroller创建的 navigationBar 的代理UINavigationBarDelegate是 navigationConroller 自身。对于代码或 IB 直接创建的 navigationBar,代理则需要自己指定。

TintColor

相信大家对tintColor这个东西肯定不会陌生,这里就不再累述,只记录一下本人之前的一个疑惑:UILabel为什么不受tintColor的影响?有位大佬在这里比较详细的讲解了,我就大概记录下自己的理解:

Apple避免在可交互元素上使用边框和渐变,取而代之使用tintColor,那么tintColor的核心思想就是区分元素是否可以响应触摸。显而易见的,UILabel是不可交互元素,即便你设置它的tintColor也不会被绘制。

VisualEffect

系统有三个关于高斯模糊效果的类,父类:UIVisualEffect,两个子类:UIBlurEffectUIVibrancyEffect

UIVisualEffectView就是展示这些效果的视图,文档里说:

Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView.

对于UIVisualEffectView,根据想要的 effect,

  • UIBlurEffect只是简单的给UIVisualEffectView后面的视图添加高斯模糊效果,对于添加到UIVisualEffectViewcontentView中的视图则不会产生模糊效果。

  • UIVibrancyEffect不会给UIVisualEffectView后面的视图产生模糊,只会使添加到contentView中的视图更加生动。

  • 对于UIBlurEffectUIVisualEffectView,若它的contentView中又包含了一个UIVibrancyEffectUIVisualEffectView。则显示效果又有模糊效果,又有生动效果。

lazy var blurContainVibrancyView: UIVisualEffectView = {
   let vibrancyEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .light)))
   let label = self.creatLabel(withText: "而卒莫消长也")
   label.center = vibrancyEffectView.contentView.center
   vibrancyEffectView.contentView.addSubview(label)

   let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
   blurEffectView.contentView.addSubview(vibrancyEffectView)
   vibrancyEffectView.frame = blurEffectView.frame
   vibrancyEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
   return blurEffectView
}()
复制代码

blurContainVibrancyView

文档指出不要给UIVisualEffectView或者它的父视图设置小于 1 的alpha值,否则 effect 可能显示不正确,或者根本不显示。但是可以设置contentView子视图的alpha(在已经尝试过的实际运用中的时候,设置 UIVisualEffectViewalpha小于 1 时,Xcode 会报警告但是透明和模糊效果都存在。设置它父视图的alpha小于 1 则没有警告但是只有透明效果)。

外观

导航栏样式 barStyle

enum UIBarStyle : Int {    
    case`default`
    case black
}
复制代码

默认白底黑字,black 样式为黑底白字。且这两种样式都默认半透明(isTranslucent = true)。

barTintColor、tintColor

barTintColor:用来导航栏背景色,不要使用backgroundColor

  • 对于半透明导航栏,设置backgroundColor(蓝色),颜色显示不正确:

半透明设置背景色

  • 对于不透明导航栏,设置backgroundColor,颜色完全不显示:

不透明设置背景色

tintColor:影响 bar 的子视图颜色。

标题文字样式

  • titleTextAttributes:常见的NSAttributedString设置。
  • setTitleVerticalPositionAdjustment(CGFloat, for: UIBarMetrics):标题竖直方向偏移量。

isTranslucent

影响navigationBar的半透明效果,默认为true

  • 对于没有明确设置isTranslucentnavigationBar,如果背景图alpha < 1,则isTranslucent = true。反之为false
  • 对于明确设置isTranslucent = true的,如果背景图为不透明,则会为背景图会被添加小于 1 的系统定义的alpha
  • 对于明确设置isTranslucent = false的,如果背景图alpha < 1,会根据barStylebarTintColor为该图片添加一个相应颜色的不透明背景。

背景图和阴影图

只有在设置过背景图片的情况下,阴影图片才会生效。单独设置阴影图片没有效果。

shadowImage的位置实际上是超出了它的父视图的,设置navigationBar.clipsToBounds = true也可以隐藏。

假设isTranslucent = true

  • 如果没有背景图片,navigationBar的子视图中将会包含一个visualEffectView用来产生模糊效果。

默认视图层级

  • 如果设置了背景图片,navigationBar的子视图中将不会包含visualEffectView,而是直接生成一个半透明的背景图。

设置背景图后的视图层级

代理

导航栏位置 barPosition

public enum UIBarPosition : Int {
    case any // 未指明的
    case bottom // 指定 bar 在父视图的底部,各种阴影都会被绘制在 bar 顶部
    case top // 指定 bar 在父视图的顶部,各种阴影都会被绘制在 bar 底部
    case topAttached // 指定 bar 和父视图都在屏幕的顶部,并且 bar 的背景会穿透状态栏
}
复制代码

barPosition其实是协议UIBarPositioning中定义的属性,UINavigationBar默认遵守了该协议,值为.top

开篇就说到,UINavigationConroller创建的 navigationBar,代理为 navigationConroller 自身。其默认实现为.topAttached

如果自己创建一个 navigationBar 并将其添加到当前控制器视图中,指定代理为当前控制器。并实

UINavigationBarDelegate

func position(for bar: UIBarPositioning) -> UIBarPosition {
    return .topAttached
}
复制代码

可以得到和原生同样的效果(图中系统 iOS 10,高度为自定义,iOS 11 显示效果不一样哟):

自己创建UINavigationBar

拦截返回操作

在项目中时常有点击导航栏返回按钮,弹出确认返回的提示,此时就需要拦截返回事件。

自定义一个NavigationBarShouldPopProtocol将是否可以 pop 的控制权限交给当前控制器,再修改UINavigationController的默认实现,每次都询问topViewController是否可以 pop。且我们可以在shouldPopWhenClickBackButton方法中做一些额外操作(比如返回false,弹出提示框)。

protocol NavigationBarShouldPopProtocol {
    func shouldPopWhenClickBackButton() -> Bool
}

// 点击 navigationBar 的 backButton 是否 pop,默认为 true
extension UIViewController: NavigationBarShouldPopProtocol {
    @objc func shouldPopWhenClickBackButton() -> Bool {
        return true
    }
}
复制代码
extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        guard let items = navigationBar.items else {
            return false
        }
        
        if viewControllers.count < items.count {
            return true
        }
        
        var shouldPop = true
        if let controller = topViewController, controller.responds(to: #selector(UIViewController.shouldPopWhenClickBackButton)) {
			// 询问是否可以 pop
            shouldPop = controller.shouldPopWhenClickBackButton()
        }
        
        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for view in navigationBar.subviews {
                if view.alpha > 0 && view.alpha < 1 {
                    view.alpha = 1
                }
            }
        }
        
        return false
    }
}
复制代码

转载于:https://juejin.im/post/5cb6dd6ee51d456e2446fc90

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值