「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。
前言
iOS 响应链是 APP 交互的响应基础,只有继承自UIResponder
的子类,才可以触发响应机制。
UIResponder
是 iOS 中用于处理用户事件的 API,可以处理触摸事件,主要是使用下面的方法
// 触摸开始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 触摸移动
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 触摸取消(在触摸结束之前)
// 某个系统事件(例如电话呼入)会打断触摸过程
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 触摸结束
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
对于触摸事件,系统提供了上面四种方法来处理。如果重写了上述方法,那么事件就会在此中断,并且不再沿着事件响应链进行传递; 如果需要继续进行传递,则需要调用 super 方法。
响应机制主要有 传递链
和 响应链
两部分构成
事件传递机制
传递链
:事件传递是从 UIApplication
管理起点, 逐级向下传递。
事件的传递是自上而下触发的 UIApplication
-> UIWindow
-> root view
-> ··· -> subview
-> first view
- 当 iOS 程序中发生触摸事件后,系统会将事件加入到 UIApplication 管理的一个任务队列中
- UIApplication 将处于任务队列最前端的事件向下分发。即 UIWindow。
- UIWindow 将事件向下分发,即 UIView。
- UIView 首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
- 遍历子控件,重复3,4两步。如果没有在子控件找到触摸点,那么自己就是事件处理者。
- 如果自己不能处理触摸响应,那么不做任何处理。
事件分发有几种情况是不作处理的
- alpha < 0.01
- userInteractionEnabled = NO
- hidden = YES
若父视图事件不做处理,(以上三种情况),那么子视图也不会传递事件,改事件传递被废弃。
事件传递的两个核心方法:
//此方法返回的view是本次点击事件需要的最佳view,用来找到最终响应的view
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
//判断一个点是否在视图范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
1.通过重写hitTest方法,可将点击的点移动到子view。
2.重写pointInside方法,可扩大按钮的点击区域。
eg: 按钮的点击区域重写,小于44*44做处理,大于不处理
下面示例是定义一个button的可点击区域
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGRect bounds = self.bounds;
CGFloat widthDelta = MAX(44 - bounds.size.width, 0);
CGFloat heightDelta = MAX(44 - bounds.size.height, 0);
bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
return CGRectContainsPoint(bounds, point);
}
hitTest:withEvent:
方法的处理流程如下:
- 先通过
pointInside:withEvent:
方法判断当前触摸点是否在当前视图内,如果返回 NO,则直接终止。hitTest:withEvent:
返回 nil。 - 若返回 YES,既可以继续往下传递,向当前视图的所有子视图发送
hitTest:withEvent:
消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕。 - 若有子视图返回非空对象,则 返回此对象,处理结束。(子视图判断过程也是通过
pointInside:withEvent:
判断的)。 - 如果所有
subview
遍历结束仍然没有返回非空对象,则hitTest:withEvent:
返回自身 (self)。
事件响应机制
响应链
: 响应链则从下向上传递。响应链是通过 nextResponder
属性组成的一个链表,点击的 view 有 superView, nextResponder 就是 superView;
响应机制自下而上 first view
-> super view
-> ··· -> view controller
-> window
-> Application
-> AppDelegate
- 通过事件传递找到的 first responder。
- 若 view 的 vc 存在,则将该事件传递给其vc响应;如若不存在,则传递给其父视图。
- 若 view 的最顶层不能处理事件,则传递给UIWindow进行处理。
- 若 UIWindow 不能处理,则传递给 UIApplication。
- 若 UIApplication 不能处理,则将该事件丢弃。