问题 之前因为某些历史原因,外卖基本禁止了所有的滑动返回手势,这次产品过来说需要开发一些正常流程的返回操作,于是在修复菜单页的过程中遇到了一些问题。菜单页可以说是整个外卖最复杂的页面,如下图
图中有p1,p2,p3,p4,p5,整个菜单页的结构是: P5 最底层,UIScrollView P4 标题,UIView P3 内容, UIView P2 tab, UIScrollView P1 page, UIScrollView
层次是:p1 + p2 –> p3 + p4 –> p5
这样就有了问题,左右滑P1,其实是UIScrollView的页面切换,分别显示,菜单,点评和商家详情。在整个P3区域进行滑动返回操作,是不起效果的。
解决 查阅资料发现,滑动返回事实上也是由存在已久的UIPanGestureRecognizer来识别并响应的,它直接与UINavigationController的view(方便起见,下文中以UINavigationController.view表示)进行绑定,因此上图中存在如下关系:
UIPanGestureRecognizer ——bind—— UIScrollView
UIScreenEdgePanGestureRecognizer ——bind—— UINavigationController.view
滑动返回无法触发,说明UIScreenEdgePanGestureRecognizer并没有接收到手势事件
UINavigationController.view —> UIViewController.view —> UIScrollView —> Screen and User’s finger
即UIScrollView的panGestureRecognizer先接收到了手势事件,直接就地处理而没有往下传递。
实际上这就是两个panGestureRecognizer共存的问题。
然后具体的解决方案就是: 在P1的继承自子UIScrollView里面添加如下方法
1
2
3
4
5
6
7
8
9
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& [otherGestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
return YES;
}
return NO;
}
貌似解决问题,但是自己看会有相应UIScrollView的操作,如 看到问题了吧,在点评页面,滑动返回的时候居然一边返回,一边切换到菜单页部分,这有点不能忍,然后继续分析,是因为:
只是做到了将手势事件往下传递,而没有关闭掉在边缘时UIScrollView对事件的响应。
事实上,对UIGestureRecognizer来说,它们对事件的接收顺序和对事件的响应是可以分开设置的,即存在接收链和响应链。接收链如上文所述,和UIView绑定,由UIView的层次决定接收顺序。
而响应链在apple君的定义下,逻辑出奇的简单,只有一个方法可以设置多个gestureRecognizer的响应关系:
1
2
3
4
// create a relationship with another gesture recognizer that will prevent this gesture's actions from being called until otherGestureRecognizer transitions to UIGestureRecognizerStateFailed
// if otherGestureRecognizer transitions to UIGestureRecognizerStateRecognized or UIGestureRecognizerStateBegan then this recognizer will instead transition to UIGestureRecognizerStateFailed
// example usage: a single tap may require a double tap to fail
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
每个UIGesturerecognizer都是一个有限状态机,上述方法会在两个gestureRecognizer间建立一个依托于state的依赖关系,当被依赖的gestureRecognizer.state = failed时,另一个gestureRecognizer才能对手势进行响应。
于是,添加如下操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在创建contentView的时候添加入如下,
UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = self.screenEdgePanGestureRecognizer;
[_contentPageView.panGestureRecognizer requireGestureRecognizerToFail:screenEdgePanGestureRecognizer];
- (UIScreenEdgePanGestureRecognizer *)screenEdgePanGestureRecognizer
{
UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = nil;
if (self.navigationController.view.gestureRecognizers.count > 0)
{
for (UIGestureRecognizer *recognizer in self.navigationController.view.gestureRecognizers)
{
if ([recognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]])
{
screenEdgePanGestureRecognizer = (UIScreenEdgePanGestureRecognizer *)recognizer;
break;
}
}
}
return screenEdgePanGestureRecognizer;
}
iOS7下滑动返回与ScrollView共存二三事
Demo