iOS 响应链研究

·  阅读 539

问题

  1. UIControl 被添加到一个带有手势的 UIView 上,并给 UIControl 添加了 target-action,那么是会执行 UIControltarget-action 还是 UIView 的手势上呢?

相信这看起来简单的第一个问题,至少 90%iOSer 都会答错。

谁来响应事件

所有继承于 UIResponder 的子类

UIButtonUIViewUIWindowUIApplication 等...

什么是事件

触摸、晃动等

graph TD
手指触摸屏幕 --> IOKit.framework封装成IOHIDEvent对象 --> 通过machport转发至SprintBoard --> 通过machport转发给当前APP的主线程

RunLoop

  1. 主线程 RunLoopSource1 触发,触发 Source1 回调: __IOHIDEventSystemClientQueueCallback()
  2. Source1 触发 Source0 回调: __UIApplicationHandleEventQueue(),将 IOHIDEvent 封装为 UIEvent
  3. Source0 的回调内部会调用 UIApplicationsendEventUIEvent 传给 UIWindow

确定谁是第一响应者

  1. 判断是否隐藏、透明、可交互,均不是
  2. 判断点击是否在视图内
  3. 如果在,以 FILO 的顺序查找,子视图是否在点击范围
  4. 如果不在,查看是否在同级的视图中

代码如下:

 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,那么这个事件会仍会被传递,否则将不再传递

当手势识别参与响应链

image.png

和响应链相关的方法

cancelsTouchesInView

默认为 true。如果设置成 false,将不会发送 touchesCancelled 给目标视图。最后的结果是手势和本身方法同时触发。

delaysTouchesBegan

默认为 false。设置为 true 后 touchesBegan 会被延迟到手势识别失败后才触发。

delaysTouchesEnded

默认为 true。设置为 false 后 touchesEnd 将立刻触发,类似双击手势将无法触发。

UIControl 和手势识别

  1. UIControl 使用 target-action 响应事件,不会再沿响应链将事件传递下去。
  2. UIControltarget-action 触发时机是在 touchesEnd 后。
  3. 上面的这条会导致,如果父视图上有手势时,UIControl 将无法执行 target-action故上面的答案是执行父视图的手势,而非 UIControltarget-action
  4. 如果 target-actiontargetnil,此时将会将此 action 将延响应链传递下去,直到被丢弃
  5. 如果 target-actiontargetself,会走 runtime 消息转发,如果最终都未找到,会报 unrecgnize selector 的异常。

研究/使用过程碰到的问题

  1. vc 中实现了 touchesBegan 方法,在内部 present 到了另一个 vcPresnet 中,那么这个原来的 vc,还是可以响应 touchesBegan 的。但是 push 到另一个 vc 中是不会触发的。

引用:

iOS | 事件传递及响应链

iOS | 响应链及手势识别

分类:
iOS
标签:
分类:
iOS
标签: