由于iPhone不像安卓手机那样底部有返回键可以点击返回上个页面,每次返回上一页都需要点击页面的导航栏返回按钮,这实在是非常不方便。。。
还好苹果提供了屏幕边缘侧滑返回手势interactivePopGestureRecognizer,在屏幕左侧边缘轻轻一划就OK了,很方便,因此这个功能对APP来说是必选的,如果没有,那这个APP估计离大限也不远了。。。😨
给大家看下我的 新(mo) iPhone(ni) X(qi) 的效果😭
该文章主要讲解:
- 导航控制器的封装
- 结合左滑抽屉手势的使用场景
- 侧滑手势的添加
- 页面越级返回问题
- 真假导航栏页面之间的侧滑返回
- 导航栏标题水平位置偏移、抖动、文字变淡等问题
- 解决scrollView滚动和边缘侧滑手势冲突问题
- 侧滑手势的禁用、启用
- 侧滑手势pop页面事件拦截方法
封装导航控制器
由于本项目是tabBar
的结构,并且首页还有一个左滑抽屉,所以我自定义一个导航控制器类NavigationController
,好处就是:
- 统一整个项目的导航栏样式,如颜色、标题字体、返回按钮等
- 统一管理左滑抽屉手势的打开与关闭
- 方便集成边缘侧滑手势的集成
初始时,进行了除侧滑手势之外的配置,注释写的比较详细,就不多说了,请看下面的代码:
class NavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// 导航栏样式
navigationBar.isTranslucent = false
navigationBar.barTintColor = UIColor.Main
navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white, NSFontAttributeName: UIFont.auto(size: 18)]
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
if viewControllers.count > 0 {
// 隐藏tabBar底部
viewController.hidesBottomBarWhenPushed = true
// 关闭首页左侧抽屉手势
drawerMenuController()?.needSwipeShowMenu = false
//设置统一的返回按钮
let backBtn = BackButton(target: self, action: #selector(backBtnClicked))
viewController.navigationItem.leftBarButtonItem = UIBarButtonItem.init(customView: backBtn)
}
super.pushViewController(viewController, animated: animated)
}
// 导航栏返回按钮点击
@objc fileprivate func backBtnClicked() {
popViewController(animated: true)
if viewControllers.count == 1 { // 打开首页左侧抽屉手势
drawerMenuController()?.needSwipeShowMenu = true
}
}
}
侧滑手势添加
-
1.实现代理
我们要用到的侧滑手势,其实就是UINavigationController
的一个属性:interactivePopGestureRecognizer
,后面我们要通过它的代理方法以及导航控制器的代理方法进行一些操作,
所以我们要先遵守这两个代理:UINavigationControllerDelegate, UIGestureRecognizerDelegate
class NavigationController: UINavigationController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.interactivePopGestureRecognizer?.delegate = self
self.delegate = self
}
}
这时push几个控制器后,滑动左侧边缘就会发现已经可以成功侧滑返回上一页了~
-
2.实现didShow viewController方法(首页没有左侧抽屉菜单的可以跳过这一步)
接下来在首页像往常一样右滑,却发现左侧抽屉菜单怎么也呼唤不出来了。。。这是因为我们之前在push方法中关闭了抽屉菜单手势,然后在点击导航栏返回按钮回到首页时打开左侧抽屉手势,而现在由于是通过侧滑手势返回的,所以并没有触发按钮点击方法。
因此我们需要捕获通过侧滑手势返回页面后的事件,然后对抽屉菜单手势进行管理,这就用到了导航控制器的代理方法didShow viewController
,法如其名,就是当控制器show的时候会就回调该方法,无论是push还是pop亦或者是切换后的appear。
// 捕获侧滑手势返回页面后的事件
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
if viewControllers.count == 1 { // 回到首页,打开左侧抽屉手势
drawerMenuController()?.needSwipeShowMenu = true
}
}
这样就能愉快的在几个首页页面右滑呼唤出抽屉菜单了。
-
3.实现gestureRecognizerShouldBegin方法
如果在首页的左侧边缘尝试滑动几下的话,就会出现页面卡死的情况,点什么都没有反应了(由于首页有抽屉菜单手势的原因所以不好触发,建议先把抽屉菜单手势关闭掉,然后在首页侧滑返回一下,一下就能触发!)
从网上搜了一下,发现是因为如果根控制器使用侧滑返回的话,UI界面就会变为假死状态,所以我们要使其在根控制器的时候不响应侧滑返回手势。
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return childViewControllers.count > 1
}
至此,对于最基本常规的情况就实现完成了,但是对于一些非常规情况就不完美了,下面说一下其他需求场景~
其他需求场景中遇到的问题
一:页面越级返回问题
从A->B->C,然后从C跳过B直接返回A的解决方法:
侧滑手势页面越级返回问题
二:真假导航栏页面之间的侧滑返回
我们的需求经常在很多时候设计中间的某个页面的导航栏是不同于统一的样式的,对于这种情况最常见的做法就是自己绘制一个假的NavigationBar,然后在该页面出现的时候隐藏导航栏,在页面消失的时候再显示出导航栏,如下:
H1Controller.swift
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.isNavigationBarHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.isNavigationBarHidden = false
}
当侧滑时导航栏会一下子突然变成上一个页面的导航栏样式,并且后一半偶尔还会出现了红色(左滑抽屉的背景颜色),蛋蛋的忧伤啊😔。。。
对于这种情况,解决办法就是不要直接设置isNavigationBarHidden为true或false,而是通过调用带有animated的set方法去设置:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
这样就比较完美了~
三:导航栏标题水平位置偏移、抖动、文字变淡等问题
之前运行的时候遇到过侧滑返回时导航栏视图、标题文字一抖动的现象,除此之外文字还会偶现偏移、变淡等问题,效果如下:
这让我很是郁闷,直到那次抱着试试看的想法连上了真机。。。😭
我才发现,这些问题都不是问题,这全都是因为模拟器的bug所导致的,在真机上并没有这个问题!!!
刚刚我安装了Xcode9,用新模拟器运行了一下,发现这个问题已经没有了,苹果已经修复了这个bug~
四:解决scrollView滚动和边缘侧滑手势冲突问题
有两个方案:
- 自定义一个UIScrollView类,重写gestureRecognizerShouldBegin方法,然后让控制器中的scrollView继承该类
- 获取ScreenEdgePanGestureRecognizer手势,再通过panGestureRecognizer.require(toFail:)方法设置手势优先级
方案1:
/// 解决滚动视图跟页面的其他滑动手势(PanGuesture)冲突问题
class RecognizerScrollView: UIScrollView {
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isKind(of: UIPanGestureRecognizer.classForCoder()) {
let pan = gestureRecognizer as? UIPanGestureRecognizer
if ((pan?.translation(in: self).x) ?? 0 > CGFloat(0)) && (self.contentOffset.x == CGFloat(0)) {
return false
}
}
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
}
方案2:
NavigationController.swift 文件
/// 获取屏幕边缘侧滑手势
func getScreenEdgePanGestureRecognizer() -> UIScreenEdgePanGestureRecognizer? {
var edgePan: UIScreenEdgePanGestureRecognizer?
if let recognizers = view.gestureRecognizers, recognizers.count > 0 {
for recognizer in recognizers {
if recognizer is UIScreenEdgePanGestureRecognizer {
edgePan = recognizer as? UIScreenEdgePanGestureRecognizer
break
}
}
}
return edgePan
}
带有scrollView的Controller.swift文件
// 解决scrollView滑动手势和屏幕边缘侧滑手势冲突问题
let navController = navigationController as? NavigationController
if let edgePan = navController?.getScreenEdgePanGestureRecognizer() {
scrollView.panGestureRecognizer.require(toFail: edgePan)
}
五:在某个页面禁用侧滑手势
有时由于一些特殊的需求,可能需要控制某个页面不让侧滑手势返回,这就需要禁用、启用侧滑手势了。
NavigationController.swift 文件
fileprivate var isEnableEdegePan = true
/// 启用、禁用屏幕边缘侧滑手势
func enableScreenEdgePanGestureRecognizer(_ isEnable: Bool) {
isEnableEdegePan = isEnable
}
// 更新该方法的判断
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if !isEnableEdegePan { // 禁用边缘侧滑手势
return false
}
return childViewControllers.count > 1
}
S1Controller.swift文件
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 禁用屏幕边缘侧滑手势
let navController = navigationController as? NavigationController
navController?.enableScreenEdgePanGestureRecognizer(false)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// 启用屏幕边缘侧滑手势
let navController = navigationController as? NavigationController
navController?.enableScreenEdgePanGestureRecognizer(true)
}
六:在返回页面时除了pop之外,还需要其他操作
返回页面现在有两种途径:点击导航栏返回按钮 或 侧滑返回
1)点击导航栏返回
由于封装了导航栏的返回按钮,所以需要在需要的控制器中重写leftBarButtonItem
,然后自定义事件。
override func viewDidLoad() {
super.viewDidLoad()
// 自定义返回按钮
let backBtn = BackButton(target: self, action: #selector(backBtnClicked))
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backBtn)
}
2)侧滑手势返回
可以通过重写didMove(toParentViewController parent: UIViewController?)
方法来监听控制器的pop操作。
该方法在push、pop、被父控制器add或者remove时都会调用,但是在pop时parent参数为空,所以可以根据parent为空来截取pop事件。
class S2Controller: UIViewController {
override func didMove(toParentViewController parent: UIViewController?) {
if parent == nil {
print("S2---Pop")
task()
}
}
}
以上就是我在项目实际开发中遇到的一些问题的总结,目前应该我们项目的需求已经足够了,后续如果有新的需求问题再补充。。。
具体场景的详细使用,请看:
github代码demo
昨天苹果发布会开完了,其实我感觉买不买iPhoneX根本不重要,日子过的开心就好,人不能活的太虚荣。🙂
(来自iPhone X) 📍青岛市人民医院肾脏摘除科住院部666床