iOS_Responder chain 响应链

前言

iOS中所有继承自UIResponder的类的实例, 都是可以响应touch事件的对象. 响应机制分为传递链响应链.

传递链: 由系统向离用户最近的view传递: UIApplication -> UIWindow -> RootViewController -> View -> ... -> Button

响应链: 由离用户最近的view向系统响应: Button -> View -> ... -> RootViewController -> UIWindow -> UIApplication


事件的传递及响应过程, 如图:

Application UIWindow RootVC View Button loop [Runloop] Application UIWindow RootVC View Button

向右指的箭头为传递链,
向左指的箭头为响应链.


Hit-Test机制: (寻找响应者)

  1. 当发生touch后, 系统会将touchUIEvent的方式, 加入到UIApplication管理的事件任务队列中(FIFO)
  2. UIApplication将出入任务队列最前端的事件向下传递, 传递给UIWindow
  3. UIWindow将事件向下传递给RootVC
  4. RootVC将事件向下传递给View
  5. 调用ViewhitTest方法, 判断当前View是否可响应事件, 再调用pointInside判断触摸点是否在自己身上,如果都满足就逆序遍历subViews, 调用其hitTest方法
  6. subViews中有返回对象的, 则表示该对象为事件的响应者(子视图返回非空对象)
  7. subViews中都没有返回对象, 则该view及为时间的响应者(子视图遍历完毕)

Hit-Test方法伪实现如下:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    
    
    print("hitTest: \(self)")
    /// 1. 判断当前view是否可响应
    guard isUserInteractionEnabled else {
    
    
        /// 不允许用户交互
        return nil
    }
    guard !isHidden else {
    
    
        /// 已隐藏
        return nil
    }
    guard alpha > 0.01 else {
    
    
        /// 不透明度小于等于 0.01
        return nil
    }
    
    /// 2. touch的坐标是否在view的frame内
    guard self.point(inside: point, with: event) else {
    
    
        return nil
    }

    /// 3. 倒序遍历子视图, 递归调用hitTest
    for subview in subviews.reversed() {
    
    
        let subPoint = self.convert(point, to: subview)
        /// 首个非空子视图, 即为 first responder
        if let fitView = subview.hitTest(subPoint, with: event) {
    
    
            return fitView
        }
    }
    /// 4. 遍历所有的子视图都没有响应 hit-testing, 则该view为 first responder
    return self
}

GitHub Demo

参考:
Using responders and the responder chain to handle events

猜你喜欢

转载自blog.csdn.net/Margaret_MO/article/details/127337591
今日推荐