先看效果图

本文中,凡是看到xxx(),即表示xxx是一个函数或者方法!!!事件分为事件传递和事件响应,其中,事件响应又称事件处理。
具体代码
FindViewViewController的代码如下:
@interface FindViewViewController : UIViewController
@end
//--------分隔符,分隔.h文件和.m文件-------------
@interface FindViewViewController ()
@end
@implementation FindViewViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
//创建view层次
[self createViewHierachy];
}
- (void)viewDidAppear:(BOOL)animated {
[self logResponderChain];
}
//通过UIResponder来连接组成响应链,打印响应链:
- (void)logResponderChain {
NSLog(@"响应链");
}
- (void)createViewHierachy {
GrayView *grayView = [[GrayView alloc] initWithFrame:CGRectMake(50.f, 100.f, 260.f, 200.f)];
RedView *redView = [[RedView alloc] initWithFrame:CGRectMake(0.f, 0.f, 120.f, 100.f)];
BlueView *blueView = [[BlueView alloc] initWithFrame:CGRectMake(140.f, 100.f, 100.f, 100.f)];
YellowView *yellowView = [[YellowView alloc] initWithFrame:CGRectMake(50.f, 360.f, 200.f, 200.f)];
[self.view addSubview:grayView];
[grayView addSubview:redView];
[grayView addSubview:blueView];
[self.view addSubview:yellowView];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"vc, %s", __func__);
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"vc, %s", __func__);
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"vc, %s", __func__);
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"vc, %s", __func__);
[super touchesCancelled:touches withEvent:event];
}
UIView+Color的代码如下:
@interface UIView (Color)
- (NSString *)bgColorString;
@end
//--------分隔符,分隔.h文件和.m文件-------------
@implementation UIView (Color)
- (NSString *)bgColorString {
if (self.backgroundColor == [UIColor redColor]) {
return @"redColorView";
} else if (self.backgroundColor == UIColor.blueColor) {
return @"blueColorView";
} else if (self.backgroundColor == UIColor.yellowColor) {
return @"yellowColorView";
} else if (self.backgroundColor == UIColor.lightGrayColor) {
return @"lightGrayColorView";
}
return nil;
}
@end
GrayView的代码如下:
@interface GrayView : UIView
@end
//--------分隔符,分隔.h文件和.m文件-------------
#import "UIView+Color.h"
@implementation GrayView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = UIColor.lightGrayColor;
}
return self;
}
//返回的是 点击区域是否在该view的范围内。
//返回YES表示点击区域在当前view中,NO表示点击区域不在当前view中
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"%@, %s", self.bgColorString, __func__);
return [super pointInside:point withEvent:event];
}
//返回的是处理事件的view的实例。
//如果返回自己,表示处理事件的是当前view。返回nil表示当前view及其子view都不处理事件。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"%@, %s", self.bgColorString, __func__);
return [super hitTest:point withEvent:event];
}
//scrollView 系统会重写它的super touch:导致不能往上传递,
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%@, %s", self.bgColorString, __func__);
[super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%@, %s", self.bgColorString, __func__);
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%@, %s", self.bgColorString, __func__);
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%@, %s", self.bgColorString, __func__);
[super touchesCancelled:touches withEvent:event];
}
@end
BlueView、RedView和YellowView的代码和上面的GrayView的代码相同,只是类名不一样,这里就不贴出来了。
操作场景
- 点击蓝色的按钮,输出结果如下:
yellowColorView, -[YellowView hitTest:withEvent:]
yellowColorView, -[YellowView pointInside:withEvent:]
lightGrayColorView, -[GrayView hitTest:withEvent:]
lightGrayColorView, -[GrayView pointInside:withEvent:]
blueColorView, -[BlueView hitTest:withEvent:]
blueColorView, -[BlueView pointInside:withEvent:]
blueColorView, -[BlueView touchesBegan:withEvent:]
lightGrayColorView, -[GrayView touchesBegan:withEvent:]
vc, -[FindViewViewController touchesBegan:withEvent:]
blueColorView, -[BlueView touchesEnded:withEvent:]
lightGrayColorView, -[GrayView touchesEnded:withEvent:]
vc, -[FindViewViewController touchesEnded:withEvent:]
- 事件传递:hitTest()和pointInSide()的调用顺序是:YellowView->GrayView->BlueView,因为BlueView包含了你所点击的区域,并且BlueView没有subviews,所以BlueView的hitTest()返回的是self,即返回自身到GrayView,GrayView的hitTest()返回的是BlueView实例。
- 事件响应:touchesBegan()的调用顺序是:BlueView->GrayView。 touchesEnded()的调用顺序是:BlueView->GrayView;
点击红色的按钮,输出结果如下:
yellowColorView, -[YellowView hitTest:withEvent:]
yellowColorView, -[YellowView pointInside:withEvent:]
lightGrayColorView, -[GrayView hitTest:withEvent:]
lightGrayColorView, -[GrayView pointInside:withEvent:]
blueColorView, -[BlueView hitTest:withEvent:]
blueColorView, -[BlueView pointInside:withEvent:]
redColorView, -[RedView hitTest:withEvent:]
redColorView, -[RedView pointInside:withEvent:]
redColorView, -[RedView touchesBegan:withEvent:]
lightGrayColorView, -[GrayView touchesBegan:withEvent:]
redColorView, -[RedView touchesEnded:withEvent:]
lightGrayColorView, -[GrayView touchesEnded:withEvent:]
-
事件传递::hitTest()和pointInSide()的调用顺序是:YellowView->GrayView->BlueView->RedView,因为RedView包含了你所点击的区域,并且BlueView没有subviews,所以BlueView的hitTest()返回的是self,即返回自身到GrayView,GrayView的hitTest()返回的是RedView实例。
-
事件响应:touchesBegan()的调用顺序是:RedView->GrayView->FindViewViewController。 touchesEnded()的调用顺序是:BlueView->GrayView->FindViewViewController;
总结: hitTest()和pointInSide()的传递机制:假设父view是A,而view B和view C都是A的子view,B先被A 添加,然后C才被A添加。当点击C时,先调用A的hitTest(),A的hitTest()里面调用[super hitTest],[super hitTest]接着调用A的pointInSide(),如果pointInSide()返回YES,说明点击位置在A里面。这里假设A的pointInSide()返回YES,A的[super hitTest]里面会逆序遍历A的子view,即先调用C的hitTest()和pointInSide()。此时的情况1:如果C的hitTest()返回了一个view实例(比如C自己的实例),那么[super hitTest]里面就会停止遍历,然后返回这个view实例,所以此时是不会调用B的hitTest()和pointInSide(),即说明该view实例作为事件的处理对象。情况2:如果C的hitTest()返回了nil,则A的[super hitTest]继续遍历到,然后调用B的hitTest()和pointInSide()。如果B返回一个实例(比如B自己的实例或者B的一个子孙实例),接着A的[super hitTest]会返回该实例,即说明该实例作为事件的处理对象。
上面的总结用伪代码表示如下:
//A的[super hitTest]方法的伪代码, 即UIView里面的hitTest()的伪代码
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
NSArray<UIView *> *subviewss = [[[self subviews] reverseObjectEnumerator] allObjects];
for (UIView *subview in subviewss) {
CGPoint convertedPoint = [self convertPoint:point toView:subview];
UIView *targetView = [subview hitTest:convertedPoint withEvent:event];
if (targetView) {
return targetView; //点击区域在子view中的某个view(可以是子view的子view的子view...),返回的是该view的实例
}
}
return self; //执行到这里说明点击区域在自己这里,而不是在子view里,所以返回的是自己
}
return nil; //执行到这里说明点击区域不在自己范围内
}
总结:
-
pointInside: withEvent: 这个方法的作用只是判断点击区域是否在当前view的范围内。
-
而 hitTest: withEvent: 这个方法的返回值很重要,这个返回值表示处理事件的实例。即这个返回值及其父view、父view的父view(直到View的VC)的touchesBegan()、touchesMoved()、touchesEnded()、touchesCancelled()依次会被调用。举例,假设VC的根view有子view A,A有子view B,B有子view C,手机触摸屏幕、滑动、然后手指离开屏幕(一个事件序列)。当手指触摸屏幕时(任意位置,不一定是触摸View A的所在区域),先调用A的hitTest: withEvent:方法。 如果A的hitTest: withEvent:方法返回的是C的实例,则方法的调用顺序是C的touchesBegan:withEvent:方法->B的touchesBegan:withEvent:方法->A的touchesBegan:withEvent:方法->VC的根view的touchesBegan:withEvent:方法->VC的touchesBegan:withEvent:方法。此时手指在滑动时,方法的调用顺序是C的touchesMoved:withEvent:方法->B的touchesMoved:withEvent:方法->A的touchesMoved:withEvent:方法->VC的根view的touchesMoved:withEvent:方法->VC的touchesMoved:withEvent:方法。此时手指离开屏幕时,方法的调用顺序是C的touchesEnded:withEvent:方法->B的touchesEnded:withEvent:方法->A的touchesEnded:withEvent:方法->VC的根view的touchesEnded:withEvent:方法->VC的touchesEnded:withEvent:方法。
-
事件响应传递: 你自定的view(直接继承自UIView)里面写的
[super touchesBegan:touches withEvent:event];、[super touchesMoved:touches withEvent:event];、[super touchesEnded:touches withEvent:event];、[super touchesCancelled:touches withEvent:event];里面都会分别调用当前view对象的[self.nextResponder touchesBegan:touches withEvent:event、[self.nextResponder touchesMoved:touches withEvent:event];、[self.nextResponder touchesEnded:touches withEvent:event];、[self.nextResponder touchesCancelled:touches withEvent:event];。但如果你的自定义view继承自UIButton,此时你自定的view里面写的[super touchesBegan:touches withEvent:event];、[super touchesMoved:touches withEvent:event];、[super touchesEnded:touches withEvent:event];、[super touchesCancelled:touches withEvent:event];里面都不会调用当前view对象的[self.nextResponder touchesBegan:touches withEvent:event、[self.nextResponder touchesMoved:touches withEvent:event];、[self.nextResponder touchesEnded:touches withEvent:event];、[self.nextResponder touchesCancelled:touches withEvent:event];。因为UIButton是UIControl(该类是UIView的子类)的子类,所以UIButton的事件不会在响应链中传递。 -
事件响应传递中遇到UIControl: 先看下面的类图。在事件响应的传递过程中,如果中途遇到一个继承与UIControl的子view,那么该子view就会终止事件响应的传递。即该子view的[super touches等4个方法]里面不会调用self.nextResponder的对应的touches等4个方法。因为UIControl类重写了
touchesBegan:touches withEvent:event、touchesMoved:touches withEvent:event、touchesEnded:touches withEvent:event、touchesCancelled:touches withEvent:event4个方法,在重写的方法里面并不会调用self.nextResponder的对应的touches等4个方法。

本文深入探讨了iOS中事件响应链的工作原理,包括事件传递和响应机制,详细分析了hitTest、pointInside及touchesBegan等方法的调用流程,通过具体代码示例,解释了不同视图层级下事件如何被正确分发。
9665

被折叠的 条评论
为什么被折叠?



