touch handling时先hitTest:withEvent:
找出hitView:
1 | - (UIView *)hitTest:withEvent: { |
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 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)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)
参考:
- Event Handling Guide for iOS
- Advanced ScrollViews and Touch Handling Techniques, WWDC2014#235
- Enhancing User Experience with Scroll Views, WWDC2012#223
- iPhone Multi-touch Event Handling
- swiping and pinching with UIWebView