UINavigationController补充

#UINavigationController补充

  • 问题1 在同一个导航栏下面,A->pushB->pushC,C->popB->popA, 其中AC均有导航栏,但是B没有.导航栏怎么优雅的隐藏.
  • 问题2 自定义返回按钮,系统滑动返回手势依然有效
  • 问题3 状态栏的隐藏

下面听我娓娓道来.

对于问题1的解答

问题一的由来是:在开发中,遇到播放器的页面,竖屏时,一般导航栏隐藏.
首先,我想到的是,关于导航栏的显示隐藏的处理要在B视图中做处理,这样对其他的页面的影响最小,也有利于以后的扩展,比如可能有更多的页面push指向B,或者pop指向B.

尝试的过程:

起初
在viewDidLoad函数里面

1
self.navigationController?.navigationBar.alpha = 0.0

在viewWillDisappear函数里面

1
self.navigationController?.navigationBar.alpha = 1.0

这样无论是 B->pushC,还是B->popA,AC两个页面的导航栏均正常显示,但是C->popB时发现B的导航栏没有隐藏掉.观察代码发现,B中导航栏的隐藏的方法只写在了viewDidLoad里面了,在C->popB时,B页面viewDidLoad的函数是不再执行,只有viewWillAppear,viewDidAppear等执行.
所以我把代码放在了viewWillAppear里面

1
self.navigationController?.navigationBar.alpha = 0.0

到了这里,导航栏貌似都显示正常,但是真机测试发现,当C->popB时,在滑动返回的过程中,导航栏立刻就隐藏掉了,即B页面的viewWillAppear立刻就执行,这就导致C->popB时的动画效果很丑,那我想是否把隐藏导航栏的方法放在 viewDidAppear里面,即

1
self.navigationController?.navigationBar.alpha = 0.0

,但是此时发现该方法执行了,但是隐藏导航栏的效果却没有,显然当页面已经完成绘制后,此方法无效.然后我想到了带有动画效果的隐藏导航栏的方法,如下

1
2
3
4
5
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: true)
}

然后运行发现,带有一个向上隐藏导航栏的动画效果.
且在多次测试中发现,显示导航栏的代码

1
self.navigationController?.navigationBar.alpha = 1.0

此方法偶尔会无效,即从B->pushC,或者B->popA时,偶尔导航栏没有正常显示.
所以最终决定在viewWillDisappear函数里面,隐藏导航栏的方法改为

1
2
3
4
5
override func viewWillDisappear(_ animated: Bool) {
super. viewWillDisappear(animated) self.navigationController?.setNavigationBarHidden(false, animated: true)
}

小结一:关于此题的解决办法时,在viewdidload函数里面,写入self.navigationController?.navigationBar.alpha = 1.0因为显然初始化的时候不希望其有动画效果.然后加上下面的代码即可完美解决此问题.

1
2
3
4
5
6
7
8
9
10
11
12
B页面出现后,隐藏导航栏
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: true)
}
B页面消失时,显示导航栏
override func viewWillDisappear(_ animated: Bool) {
super. viewWillDisappear(animated) self.navigationController?.setNavigationBarHidden(false, animated: true)
}

引申,ios中很多属性的赋值,有2种,一种是直接赋值,一种是通过set方法.2种方法的区别就是有无动画.比如scrollview设置其偏移量.前后两种达到的效果是一样的.但是前者会导致一个Bug,即scrollViewDidScroll会根据偏移量的个数走几次.所以最终采用uiview动画和直接赋值结合.

代码如下:

1
2
3
4
5
//self.subContainerView.setContentOffset(CGPoint(x:kSCREENWIDTH,y:0), animated: true)
改为:
UIView.animate(withDuration: 0.3, animations: {
self.subContainerView.contentOffset = CGPoint(x:kSCREENWIDTH,y:0)
})

对于问题2的回答

之前在iOS-UINavigationController官方文档分析大总结 中我写到若是想要使用系统的滑动返回,最好不要自定义,我觉得有必要更正一下.
在项目开发中,你会发现一般导航栏的背景色,返回按钮 都需要自定义处理.

在项目基类导航视图控制器里面,代码如下即可.

1
self.navigationBar.barTintColor = kColor(r: 39, g: 39, b: 40, alpha: 1.0)

在基类视图控制器里面,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var showBack = true {
willSet{
if newValue {
initCustomNavigationBar()
}else{
navigationItem.leftBarButtonItem = nil
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//改变状态栏的字体颜色
self.navigationController?.navigationBar.barStyle = .blackOpaque;
self.view.backgroundColor = kColor(r: 241)
//FIXME:这个添加后 坐标是以 64为坐标原点 但是 导航栏 tabBar的颜色发生莫名变化 整体变灰 故暂时舍弃
//后来发现暂时还可以
self.edgesForExtendedLayout = UIRectEdge(rawValue: 0)//基础从0开始-,-
navigationController?.interactivePopGestureRecognizer?.delegate = self//自定义navigationbar后需要滑动返回的话自己设定返回true吧
showBack = true
}
//MARK: - custom view
func initCustomNavigationBar() {
// 返回按钮的自定义
let backButton = UIButton()
backButton.xlpInitButton(CGRect(x: 0, y: 0, width: 44, height: 44), title: nil, titleColor: .white, fontSize: 16, backgroundColor: .clear, imageStr: "arrow_left", backgroundImageStr: nil, cornerRedius: nil, superView: nil) { (bt) in
self.navigationController?.popViewController(animated: true)
}
backButton.imageEdgeInsets = UIEdgeInsets.init(top: 0, left: -20, bottom: 0, right: 22)
let backItem = UIBarButtonItem.init(customView: backButton)
navigationItem.leftBarButtonItem = backItem
}
func initLeftBarItem(_ name:String?,imageName:String?,leftAction:@escaping WMButtonClickBlock) {
let leftButton = UIButton()
leftButton.xlpInitButton(CGRect(x: 0, y: 0, width: 44, height: 44), title: name, titleColor: .white, fontSize: 16, backgroundColor: nil, imageStr: imageName, backgroundImageStr: nil, cornerRedius: nil, superView: nil, buttonClick: leftAction)
leftButton.imageEdgeInsets = UIEdgeInsets.init(top: 0, left: -20, bottom: 0, right: 22)
let lefItem = UIBarButtonItem.init(customView: leftButton)
navigationItem.leftBarButtonItem = lefItem
}

默认是显示自定义的返回按钮,在不需要显示返回按钮的时候比如首页,则直接showBack = false 即可.
当需要自定义返回按钮,则调用initLeftBarItem(_ name:String?,imageName:String?,leftAction:@escaping WMButtonClickBlock)即可.
包括自定义导航栏右按钮皆可以在基类里面定义好,然后在子类里面直接调用即可.

对于问题3的回答

项目中涉及到播放器的全屏与竖屏之间切换,经过调研以及研究市场主流的播放器的横竖屏切换发现,横竖屏切换最好的解决办法是旋转播放器,然后更新播放器的约束,这个时候发现状态栏还是竖屏的状态,显然需要隐藏掉.

解决办法为:

创建变量标志是否全屏,在需要全屏的时候,该标志位置为true,同时在
在info.plist中View controller-based status bar appearance 设置为NO

1
2
3
4
5
6
var isFullscreen: Bool = false {
didSet {
// 为了隐藏状态栏必须在info.plist中View controller-based status bar appearance 设置为NO
UIApplication.shared.setStatusBarHidden(isFullscreen, with: .slide)
}
}

若在这个页pop到其他页面时发现状态栏消失,需要在返回的方法里面主动将该标志位置为true.

坚持原创技术分享,您的支持将鼓励我继续创作!