在开始写UINavigationBar
以前,了解几个导航栏中用到的知识,将会更有利于理解。ios
首先须要明确UINavigationBar
是能够脱离UINavigationConroller
单独做为控件的。只是UINavigationConroller
建立的 navigationBar 的代理UINavigationBarDelegate
是 navigationConroller 自身。对于代码或 IB 直接建立的 navigationBar,代理则须要本身指定。swift
相信你们对tintColor
这个东西确定不会陌生,这里就再也不累述,只记录一下本人以前的一个疑惑:UILabel
为何不受tintColor
的影响?有位大佬在这里比较详细的讲解了,我就大概记录下本身的理解:app
Apple
避免在可交互元素上使用边框和渐变,取而代之使用tintColor
,那么tintColor
的核心思想就是区分元素是否能够响应触摸。显而易见的,UILabel
是不可交互元素,即使你设置它的tintColor
也不会被绘制。async
系统有三个关于高斯模糊效果的类,父类:UIVisualEffect
,两个子类:UIBlurEffect
和UIVibrancyEffect
。flex
UIVisualEffectView
就是展现这些效果的视图,文档里说:ui
Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView.spa
对于UIVisualEffectView
,根据想要的 effect,3d
UIBlurEffect
只是简单的给UIVisualEffectView
后面的视图添加高斯模糊效果,对于添加到UIVisualEffectView
的contentView
中的视图则不会产生模糊效果。代理
UIVibrancyEffect
不会给UIVisualEffectView
后面的视图产生模糊,只会使添加到contentView
中的视图更加生动。code
对于UIBlurEffect
的UIVisualEffectView
,若它的contentView
中又包含了一个UIVibrancyEffect
的UIVisualEffectView
。则显示效果又有模糊效果,又有生动效果。
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
}()
复制代码
文档指出不要给UIVisualEffectView
或者它的父视图设置小于 1 的alpha
值,不然 effect 可能显示不正确,或者根本不显示。可是能够设置contentView
子视图的alpha
(在已经尝试过的实际运用中的时候,设置 UIVisualEffectView
的alpha
小于 1 时,Xcode 会报警告可是透明和模糊效果都存在。设置它父视图的alpha
小于 1 则没有警告可是只有透明效果)。
enum UIBarStyle : Int {
case`default`
case black
}
复制代码
默认白底黑字,black 样式为黑底白字。且这两种样式都默认半透明(isTranslucent = true
)。
barTintColor
:用来导航栏背景色,不要使用backgroundColor
。
backgroundColor
(蓝色),颜色显示不正确:backgroundColor
,颜色彻底不显示:tintColor
:影响 bar 的子视图颜色。
titleTextAttributes
:常见的NSAttributedString
设置。setTitleVerticalPositionAdjustment(CGFloat, for: UIBarMetrics)
:标题竖直方向偏移量。影响navigationBar
的半透明效果,默认为true
。
isTranslucent
的navigationBar
,若是背景图alpha < 1
,则isTranslucent = true
。反之为false
。isTranslucent = true
的,若是背景图为不透明,则会为背景图会被添加小于 1 的系统定义的alpha
。isTranslucent = false
的,若是背景图alpha < 1
,会根据barStyle
或barTintColor
为该图片添加一个相应颜色的不透明背景。只有在设置过背景图片的状况下,阴影图片才会生效。单独设置阴影图片没有效果。
shadowImage
的位置其实是超出了它的父视图的,设置navigationBar.clipsToBounds = true
也能够隐藏。
假设isTranslucent = true
。
navigationBar
的子视图中将会包含一个visualEffectView
用来产生模糊效果。navigationBar
的子视图中将不会包含visualEffectView
,而是直接生成一个半透明的背景图。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 显示效果不同哟):
在项目中时常有点击导航栏返回按钮,弹出确认返回的提示,此时就须要拦截返回事件。
自定义一个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
}
}
复制代码