iOS 事件传递和响应

2020-05-10  本文已影响0人  游城十代2dai

0x00 事件

iOS 中的事件需要继承 UIResponder, 凡是响应者都会以下方法:

// 触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

// 传感器事件(摇一摇的实现)
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

// 远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;

0x01 传递中寻找合适的 View

主要通过以下方法寻找合适的 View:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

其中, hitTest 方法是从 Window 开始查找合适的 View

pointInside, 是判断点是否在当前控件内, 并且是否能响应事件

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *fitView = nil;
    // 1. 判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return fitView;
    // 2. 判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return fitView;
    // 3. 倒序遍历当前控件的子控件
    NSUInteger count = self.subviews.count;
    for (NSUInteger i = count - 1; i >= 0; --i) {
        UIView *childView = self.subviews[i];
        // 4. 转换子控件坐标, 并调用 hitTest
        CGPoint childPoint = [self convertPoint:point toView:childView];
        fitView = [childView hitTest:childPoint withEvent:event];
        // 5. 寻找到最合适的 view
        if (fitView) {
            return fitView;
        }
    }
    // 6. 循环结束, 表示没有比自己更合适的 view
    return self;
}

0x02 响应链

0x03 Final

产生触摸事件 ➡️
UIApplication 事件队列 ➡️
[UIWindow hitTest:withEvent:] ➡️
返回更合适的 view ➡️
[子控件 hitTest:withEvent:] ➡️
返回最合适的 view ➡️
最合适的 view 调用 touches 方法 ➡️
判断是否实现 touches 方法 ➡️
没有实现默认会将事件传递给上一个响应者 ➡️
找到上一个响应者 ➡️
找不到方法作废

找最合适的 View 是从根往枝叶上去查找, 最合适的 View 响应事件传递是从自己查找 NextResponder

上一篇下一篇

猜你喜欢

热点阅读