Touch Handling 笔记

touch handling时先hitTest:withEvent:找出hitView:

1
2
3
4
5
6
7
8
9
10
- (UIView *)hitTest:withEvent: {
if /* pointInside:withEvent:, eg. point is in our bounds */ {
for /* each subview, in reverse order (eg. from top to bottom) */ {
hitView = /* recursive call hitTest:withEvent: on subview */
if (hitView != nil) return hitView;
}
return self;
}
return nil;
}

Responder Chain

然后UIWindow就sendEvent:给这个view。hitView遵循responderChain:

  • 若hitView实现了touchesBegan:,不管有没实现其他3个方法:调hitView的touchesBegan:方法,再调hitView的其他3个方法(如果实现了的话)
  • 若hitView没实现touchesBegan:,但实现了其他3个方法:沿chain找到第一个响应touchesBegan:的ancestorView,其他3个方法在沿着chain从[hitView -> ancestorView]的各个view上依次调用。

responderChain基本就是沿着superview转发touch。如果这个superview是viewController.view,那实际上是将touch交给viewController处理。例如,parentView -> parent’s viewController -> window -> application -> applicationDelegate。

但我们不能手动将touch传给nextResponder(下面的写法不太work):

1
2
3
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesBegan:touches withEvent:event];
}

Gesture Recognizer

gestureRecognizer不依赖responderChain。从[hitView -> window]链上的所有gestureRecognizers都可以处理touch。touchesBegan:touchesMoved:会同时发给hitView和[hitView -> window]链上的所有gestureRecognizers。在touchesEnded阶段,如果沿链有gestureRecognizer识别成功就给hitView发touchesCancelled:(默认gestureRecognizer.cancelsTouchesInView == YES),如果识别失败就给hitView发touchesEnded:withEvent:

gestureRecognizer默认不能与其他gestureRecognizers同时识别成功,可以重载delegate方法:gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:

gestureRecognizer的默认属性:

  • cancelsTouchesInView (default YES),识别成功时向touch.view发touchesCancelled:
  • delaysTouchesBegan (default NO),touchesBegan:会同时发往touch.view,若设为YES则得等自己识别失败才把touchesBegan:发往touch.view
  • delaysTouchesEnded (default YES),例如,这样就不会把双击识别成两次单击

UIScrollView

scrollView不管有没实现touchesBegan:等4个方法,都会自己处理touch不会向responderChain转发。这很可能是因为scrollView默认实现了touchesBegan:方法。

webView即使实现了touchesBegan:等4个方法也不会调用到,touch都被隐藏嵌套的_UIWebViewScrollView: UIWebScrollView : UIScrollView)吞掉了,交给干活的子view,如UIWebBrowserView: UIWebDocumentView : UIWebTiledView : UIView)处理。由于webView内部scrollView会吞掉touch,甚至在touchMoved几次后把touch.view变成nil。要对将传给webView的touch做其他处理,可重载UIWindow的sendEvent:,先截获感兴趣的touch做处理,再[super sendEvent:event];走正常流程将touch传给webView。

默认scrollView.delaysContentTouches == YES,即scrollView有个内装定时器的gestureRecognizer,设置有gestureRecognizer.delaysTouchesBegan = YES,要等这小定时器超时gestureRecognizer识别失败才给view转发touchesBegan:消息。当快速滚动scrollView时,panGestureRecognizer立即识别成功,识别成功的gestureRecognizer默认cancelsTouchesInView。由于touchesBegan:消息还没发出,不再发送touchesCancelled:,直接把touchesBegan:消息丢掉。

Others

若target小于44pt不好点中,应重载target的pointInside:withEvent:来扩展可点击区域CGRectInset(self.bounds, -expansion, -expansion)


参考: