问题
- 当
UIControl被添加到一个带有手势的UIView上,并给UIControl添加了target-action,那么是会执行UIControl的target-action还是UIView的手势上呢?
相信这看起来简单的第一个问题,至少 90% 的 iOSer 都会答错。
谁来响应事件
所有继承于 UIResponder 的子类
UIButton、UIView、UIWindow、UIApplication 等...
什么是事件
触摸、晃动等
graph TD
手指触摸屏幕 --> IOKit.framework封装成IOHIDEvent对象 --> 通过machport转发至SprintBoard --> 通过machport转发给当前APP的主线程
RunLoop
- 主线程
RunLoop的Source1触发,触发Source1回调:__IOHIDEventSystemClientQueueCallback() Source1触发Source0回调:__UIApplicationHandleEventQueue(),将IOHIDEvent封装为UIEventSource0的回调内部会调用UIApplication的sendEvent将UIEvent传给UIWindow
确定谁是第一响应者
- 判断是否隐藏、透明、可交互,均不是
- 判断点击是否在视图内
- 如果在,以
FILO的顺序查找,子视图是否在点击范围 - 如果不在,查看是否在同级的视图中
代码如下:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
print(classTag, #function, self.point(inside: point, with: event))
if isHidden || !isUserInteractionEnabled || alpha <= 0.01 {
return nil
}
if self.point(inside: point, with: event) {
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
let resultView = subview.hitTest(convertedPoint, with: event)
if let resultView = resultView {
return resultView
}
}
return self
}
return super.hitTest(point, with: event)
}
沿响应链传递事件
确定响应链的成员
第一响应者 -> 第一响应者父视图 -> 父视图的父视图 -> UIViewController -> UIWindow -> UIApplication
可以理解为 nextResponder 就是 superView
传递事件的方法
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
如果调用上述方法的 super,那么这个事件会仍会被传递,否则将不再传递
当手势识别参与响应链
和响应链相关的方法
cancelsTouchesInView
默认为 true。如果设置成 false,将不会发送 touchesCancelled 给目标视图。最后的结果是手势和本身方法同时触发。
delaysTouchesBegan
默认为 false。设置为 true 后 touchesBegan 会被延迟到手势识别失败后才触发。
delaysTouchesEnded
默认为 true。设置为 false 后 touchesEnd 将立刻触发,类似双击手势将无法触发。
UIControl 和手势识别
UIControl使用target-action响应事件,不会再沿响应链将事件传递下去。UIControl的target-action触发时机是在touchesEnd后。- 上面的这条会导致,如果父视图上有手势时,
UIControl将无法执行target-action。故上面的答案是执行父视图的手势,而非UIControl的target-action。 - 如果
target-action的target为nil,此时将会将此action将延响应链传递下去,直到被丢弃 - 如果
target-action的target为self,会走runtime消息转发,如果最终都未找到,会报unrecgnize selector的异常。
研究/使用过程碰到的问题
vc中实现了touchesBegan方法,在内部present到了另一个vcPresnet中,那么这个原来的vc,还是可以响应touchesBegan的。但是push到另一个vc中是不会触发的。
引用: