响应链和事件传递

一. iPhone操作系统下的事件

事件是被发送到应用程序中反应用户操作的对象。当用户触发一个事件时,UIKit创建一个包含处理事件信息的事件对象。然后把它放在活跃app的时间队列中。像触摸事件,是被打包在UIEvent对象中的一组touches。像运动事件,事件对象的不同取决于你使用哪个框架和对什么类型的运动事件感兴趣。   

事件沿着特定的路径传递直到被传递给一个能处理它的对象。 最初,UIApplication单例对象从队列头部接收事件并派发下去处理。通常,事件被传递给app的主窗口,它将事件传递给初始对象处理,这取决于事件的类型。

触摸事件

对于触摸事件而言, 窗口对象首先试着将其传递给触摸发生的视图,这个视图被称为 hit-test view。查找 hit-test view的过程被称为hit-testing。

运动和远程控制事件

对于这类事件,窗口对象将摇晃运动或者远程控制事件传递给第一响应者来处理。

事件传递的最终目的是找出一个能处理并响应事件的对象。UIKit 会将事件传递给最适合处理该事件的对象,对于触摸事件,该对象往往是被触摸的视图;对于其他事件,该对象往往是第一响应者。

二. 通过hit-testing找被触摸的视图

iPhone操作系统通过 hit-testing 过程来找出被触摸的视图。该过程会检查触摸点是否位于相关视图的范围之内。如果触摸点位于视图范围内,则进一步对其子视图执行检查过程,最终,触摸点所在的视图层级最底层的视图(对于界面来说是最上层的视图)将成为 hit-test 视图,iPhone操作系统会将触摸事件传递给该视图进行处理。

例如,图一的视图层级为:View A是最底层的视图,先添加View B, 再添加View C; 接着在View C上添加View D, 接着添加View E.

场景一:用户触摸了View B. iPhone操作系统按照如下过程查找hit-test视图:

1. 判断触摸点是否位于 View A 内,若是则进一步检查子视图 View B 和 View C;

2. 基于“同一层级的视图,后添加地先被检查”的原则,先检查触摸点是否在View C上,发现并不在;

3. 检查View B, 发现触摸点在其范围内,并且ViewB是触摸点在的视图中最表层的视图,因此它就是hit-test 视图。


场景二:用户触摸了View E. iPhone操作系统按照如下过程查找hit-test视图:

1.  判断触摸点是否位于 View A 内,若是则进一步检查子视图 View B 和 View C;

2.  基于“同一层级的视图,后添加地先被检查”的原则,先检查触摸点是否在View C上,发现确实是在 View C 内,因此进一步检查子视图;

3.  View E是后添加在View C上,因此先被检查。发现触摸点确实是在View E 上, 并且View E是视图中最表层的视图,因此它就是 hit-test 视图。


例如,图二的视图层级为:View A是最底层的视图,View B是View A的子视图, View C是View B的子视图。

场景一:用户触摸了View C(未与View B重合的部分). iPhone操作系统按照如下过程查找hit-test视图:

1.  判断触摸点是否位于 View A 内,若是则进一步检查子视图 View B ;

2. 判断触摸点不再View B内,因此也不再检查View C. View A就被视作hit-test视图.

Tips: 如果某个子视图的一部分位于父视图范围之外,在父视图的 clipsToBounds 属性关闭的情况下,超出父视图范围的这部分子视图不会被裁剪掉,但是此时触摸该子视图位于父视图之外的部分将没有任何反应(接下来会结合hit-testing来具体说明)。

        上述查找过程主要利用 hitTest(_:withEvent:) 方法,该方法会根据给定的 CGPoint 和 UIEvent 返回 hit-test 视图。

        首先,该方法会向接收者发送 pointInside(_:withEvent:) 消息,如果传入的 CGPoint(即触摸点)不在接收者(也就是某个视图)的范围内,pointInside(_:withEvent:) 将会返回 false, hitTest(_:withEvent:) 就会直接返回 nil,不会进一步检查子视图。

         如果传入的 CGPoint(即触摸点)位于接收者(也就是某个视图)的范围内,pointInside(_:withEvent:) 将会返回 true。然后,按照子视图添加顺序的相反顺序(即先添加的子视图后检查触摸点是否在当前视图范围内),对每个子视图发送 hitTest(_:withEvent:) 消息,进一步检查子视图。如上递归过程结束后,最初调用的 hitTest(_:withEvent:) 方法将会返回 hit-test 视图(hit-test 视图在hitTest(_:withEvent:) 中返回自己,其父视图将其返回给上一级父视图...)。如上所述的递归判断过程类似下面这样:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self isUserInteractionEnabled]) {
        return nil;
    } else {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
}

    hit-test 视图拥有最先处理触摸事件的机会,之后,还可以选择将触摸事件沿响应者链条传递给下一个响应者,例如 hit-test 视图的父视图。默认情况下,触摸事件被处理后不会传递给下一个响应者。

三.由响应者组成的 响应者链条

UIResponder 类及其子类的对象,都可以被称之为响应者对象,它们具有响应和处理事件的能力。UIApplication、 UIViewController 以及 UIView 都是 UIResponder 的子类,这意味着所有视图控制器和视图都是响应者对象。不过, CALayer 直接继承自 NSObejct,因此它不是响应者对象。

响应者链条即由这样一系列响应者对象链接在一起, 开始于第一响应者对象,结束于 UIApplication 单例对象(实际上往往结束于它的代理对象)。如果第一响应者无法处理事件,事件就会沿响应者链条往下传递,即传递给下一个响应者, 从而传递事件。 

第一响应者即是第一个有机会处理事件的响应者对象。通常情况下,它是一个视图。一个响应者对象若想成为第一响应者,需满足如下两点:

  1. 重写 canBecomeFirstResponder() 方法并返回 true。UIResponder 的默认实现是返回 false。

  2. 收到 becomeFirstResponder() 消息。在某些情况下,往往会手动给响应者发送此消息,从而主动成为第一响应者。

除了传递事件,响应者链条还会传递一些别的东西,具体说来,响应者链条可传递如下事件或者消息:

触摸事件

要处理触摸事件,响应者可以重写 touchesBegan(_withEvent:) 系列方法。

-(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;

如果 hit-test 对象不处理触摸事件,触摸事件将沿着响应者链条传递给下一响应者。

运动事件

要处理运动事件,响应者需要重写 motionBegan(_:withEvent:) 系列方法。

-(void)motionBegan:(UIEventSubtype)motionwithEvent:(UIEvent*)event;
-(void)motionEnded:(UIEventSubtype)motionwithEvent:(UIEvent*)event;
-(void)motionCancelled:(UIEventSubtype)motionwithEvent:(UIEvent*)event;

如果第一响应者不处理事件,运动事件将沿着响应者链条传递给下一响应者。

远程控制事件

要处理远程控制事件,响应者需要实现 remoteControlReceivedWithEvent(_:) 方法。在 iOS 7.1 之后,最好使用 MPRemoteCommandCenter 获取多媒体控制操作对应的 MPRemoteCommand 对象,并注册相应的回调方法或者 block。

动作消息

当用户操作某个控件后,例如按钮或者开关,如果该控件的 target 为 nil,那么控件绑定的方法将会从控件开始,沿着响应者链向下传递,寻找一个实现了相应方法的响应者。

编辑菜单消息

当用户点击了编辑菜单的某个命令后,iOS 通过响应者链条来寻找一个实现了相应方法(例如 cut(_:), copy(_:),paste(:_))的响应者。

文本编辑

当用户点击了 UITextField 或者 UITextView 后,该控件会自动成为第一响应者,弹出虚拟键盘并成为输入焦点。

当用户点击 UITextField 或者 UITextView 后,它们会自动成为第一响应者,而其他响应者则需通过接收 becomeFirstResponder() 消息来成为第一响应者。

四.事件在响应者链条上的传递路径

如果某个应该处理事件的响应者(无论是 hit-test 视图还是第一响应者)不处理事件,UIKit 就会将事件沿着响应者链条向下传递,直到找到处理事件的响应者,或者再没有下一个响应者。每个响应者都可以决定是否处理传给自己的事件,以及是否将事件传递给下一个响应者,即 nextResponder 属性所引用的响应者。

如左图所示,事件按如下路径进行传递:

        1. 绿色的 initial view 优先处理事件,如果不处理事件,就将事件传递给父视图,因为它并非视图控制器所属的视图。

        2. 黄色的视图有机会处理事件,如果不处理事件,它也将事件传递给父视图,因为它也不是视图控制器所属的视图。

        3. 蓝色的视图有机会处理事件,如果不处理事件,因为它是视图控制器所属的视图,它会将事件传递给视图控制器,而非自己的父视图。

        4. 视图控制器有机会处理事件,如果不处理事件,它将事件传递给自己的视图的父视图,在这种情况下即是主窗口。

        5. 主窗口有机会处理事件,如果不处理事件,它将事件传递给 UIApplication 单例对象。

        6. UIApplication 单例对象有机会处理事件,如果不处理事件,并且应用代理也是 UIResponder 子类,它会将事件传递给应用代理。

        7. 如果到最后也没有响应者处理事件,事件就会被丢弃。

        对于右图所示,事件传递过程大同小异,只不过靠左的视图控制器的视图的父视图不是主窗口而是另一个视图控制器的视图,因此它将事件传递给靠右的视图控制器的视图。

        如上所述,事件传递的规律是,一个视图将事件传递给父视图,如果自己是视图控制器的视图,则先传给视图控制器,再传递给父视图。主窗口将事件传给 UIApplication 单例对象,后者再进一步传递给 UIApplicationDelegate,前提是应用代理是 UIResponder 的子类。

Tips:  前面提到的“不处理事件”,是指响应者没有对 UIResponder 的相应事件处理方法进行重写。例如,要处理触摸事件,需重写 touchesBegan(_:withEvent:) 系列方法。虽然 UIResponder 的默认实现为空,但是它的某些子类,例如 UIView、UIWindow、UIViewController 和 UIApplication,默认会将事件传递给下一响应者。对于这些子类,重写事件处理方法后,若想继续传递事件,应该调用超类方法,而不是通过 nextResponder 拿到下一响应者来手动调用其相关方法。


链接:https://www.jianshu.com/p/e84e3f47a040

 

这里附上代码 

图如上所示,1最先加入,2和3依次加入到1中,4和5依次加入到2中,6和7依次加入到3中。这里只展示部分一个视图的.m其余一样

#import "PurplelView.h"

@implementation PurplelView

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self isUserInteractionEnabled]) {
        return nil;
    } else {
        NSLog(@"%@---hitTest",[self class]);
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            NSLog(@"%@---wai",[subview class]);
            UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
            NSLog(@"%@---cycle",[self class]);

            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
}
@end

调用

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self drawLine];
    self.view.backgroundColor = [UIColor whiteColor];
//    [self setUpTheAAChartViewWithChartType222];
    
    
    
    PurplelView *purple = [[PurplelView alloc] init];
    purple.backgroundColor = [UIColor purpleColor];
    [self.view addSubview:purple];
    [purple mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view).offset(10);
        make.top.equalTo(self.view).offset(100);
        make.width.equalTo(@(500));
        make.height.equalTo(@700);
    }];
    
    RedView *red = [[RedView alloc] init];
    red.backgroundColor = [UIColor redColor];
    [purple addSubview:red];
    [red mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(purple).offset(100);
        make.top.equalTo(purple).offset(10);
        make.width.equalTo(@(400));
        make.height.equalTo(@200);
    }];
    
    YellowView *yellow = [[YellowView alloc] init];
    yellow.backgroundColor = [UIColor yellowColor];
    [purple addSubview:yellow];
    [yellow mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(purple).offset(100);
        make.top.equalTo(red.mas_bottom).offset(200);
        make.width.equalTo(@(390));
        make.height.equalTo(@400);
    }];
    
    
    BlueView *blue = [[BlueView alloc] init];
    blue.backgroundColor = [UIColor blueColor];
    [red addSubview:blue];
    [blue mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(red).offset(100);
        make.top.equalTo(red).offset(10);
        make.width.equalTo(@(100));
        make.height.equalTo(@90);
    }];
    
    GreenView *green = [[GreenView alloc] init];
    green.backgroundColor = [UIColor greenColor];
    [red addSubview:green];
    [green mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(red).offset(100);
        make.top.equalTo(blue.mas_bottom).offset(30);
        make.width.equalTo(@(130));
        make.height.equalTo(@120);
    }];
    
    BrownView *brown = [[BrownView alloc] init];
    brown.backgroundColor = [UIColor brownColor];
    [yellow addSubview:brown];
    [brown mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(yellow).offset(100);
        make.top.equalTo(yellow).offset(10);
        make.width.equalTo(@(130));
        make.height.equalTo(@120);
    }];
    
    GrayView *gray = [[GrayView alloc] init];
    gray.backgroundColor = [UIColor grayColor];
    [yellow addSubview:gray];
    [gray mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(red).offset(100);
        make.top.equalTo(brown.mas_bottom).offset(30);
        make.width.equalTo(@(130));
        make.height.equalTo(@120);
    }];
    
}

点击棕色,打印

2021-03-10 17:03:21.432013+0800 test[7818:174931] PurplelView---hitTest
2021-03-10 17:03:21.432206+0800 test[7818:174931] YellowView---wai
2021-03-10 17:03:21.432459+0800 test[7818:174931] YellowView---hitTest
2021-03-10 17:03:21.432643+0800 test[7818:174931] GrayView---wai
2021-03-10 17:03:21.432815+0800 test[7818:174931] YellowView---cycle
2021-03-10 17:03:21.432964+0800 test[7818:174931] BrownView---wai
2021-03-10 17:03:21.433120+0800 test[7818:174931] BrownView---hitTest
2021-03-10 17:03:21.433246+0800 test[7818:174931] YellowView---cycle
2021-03-10 17:03:21.433365+0800 test[7818:174931] PurplelView---cycle
2021-03-10 17:03:21.433715+0800 test[7818:174931] PurplelView---hitTest
2021-03-10 17:03:21.433841+0800 test[7818:174931] YellowView---wai
2021-03-10 17:03:21.433959+0800 test[7818:174931] YellowView---hitTest
2021-03-10 17:03:21.434195+0800 test[7818:174931] GrayView---wai
2021-03-10 17:03:21.434474+0800 test[7818:174931] YellowView---cycle
2021-03-10 17:03:21.434777+0800 test[7818:174931] BrownView---wai
2021-03-10 17:03:21.435095+0800 test[7818:174931] BrownView---hitTest
2021-03-10 17:03:21.435423+0800 test[7818:174931] YellowView---cycle
2021-03-10 17:03:21.435720+0800 test[7818:174931] PurplelView---cycle

 

                                      灰色:触摸点没在自己身上,停止

紫色-> {  黄色   -> { 

                                      棕色:触摸点在自己身上 正常返回

这样的方式是因为代码中重写的是后遍历,后面加入的在前面先判断 

 接下来 点击蓝色

2021-03-10 17:07:48.456816+0800 test[7818:174931] PurplelView---hitTest
2021-03-10 17:07:48.457067+0800 test[7818:174931] YellowView---wai
2021-03-10 17:07:48.457244+0800 test[7818:174931] PurplelView---cycle
2021-03-10 17:07:48.457418+0800 test[7818:174931] RedView---wai
2021-03-10 17:07:48.459131+0800 test[7818:174931] RedView---hitTest
2021-03-10 17:07:48.459297+0800 test[7818:174931] GreenView---wai
2021-03-10 17:07:48.459416+0800 test[7818:174931] RedView---cycle
2021-03-10 17:07:48.459565+0800 test[7818:174931] BlueView---wai
2021-03-10 17:07:48.460238+0800 test[7818:174931] BlueView---hitTest
2021-03-10 17:07:48.460382+0800 test[7818:174931] RedView---cycle
2021-03-10 17:07:48.460514+0800 test[7818:174931] PurplelView---cycle
2021-03-10 17:07:48.460800+0800 test[7818:174931] PurplelView---hitTest
2021-03-10 17:07:48.460921+0800 test[7818:174931] YellowView---wai
2021-03-10 17:07:48.461166+0800 test[7818:174931] PurplelView---cycle
2021-03-10 17:07:48.461573+0800 test[7818:174931] RedView---wai
2021-03-10 17:07:48.461973+0800 test[7818:174931] RedView---hitTest
2021-03-10 17:07:48.462266+0800 test[7818:174931] GreenView---wai
2021-03-10 17:07:48.462588+0800 test[7818:174931] RedView---cycle
2021-03-10 17:07:48.462842+0800 test[7818:174931] BlueView---wai
2021-03-10 17:07:48.463134+0800 test[7818:174931] BlueView---hitTest
2021-03-10 17:07:48.465694+0800 test[7818:174931] RedView---cycle
2021-03-10 17:07:48.465821+0800 test[7818:174931] PurplelView---cycle

 

 

                      黄色:触摸点没有在自己身上,不再往下走,截止

紫色-> {                                   绿色:触摸点没在自己身上,不再往下走,截止

                     红色:-> {

                                                 蓝色: 在自己身上,可处理,

类似于二分法的先序处理,只是有前提条件,不会走完。

 

五:父视图和子视图同时响应点击事件

因为hit方法找到合适的view了之后,就开始找实现了view是不是实现了touch方法,如果当前view没有实现touch会一直向上寻找到实现touch方法的视图,都没有实现的话就返回到uiapplcation了,所以如果想要父视图和子视图一起响应点击事件,只需要两个同时实现touch方法,然后在子view的touch方法里调取[super touchesBegan:touches withEvent:event]这个方法就可以。

六:子视图超出父视图,超出部分点击响应事件

在开发的过程中,不免会遇到子控件超过父视图的情况。都知道,超出父视图的部分是不能响应点击事件,但是总有些情况需要我们让超出的部分响应点击事件,那么convertPoint就可以大显身手了。小白可参考,大神请指点。:这里为什么超出的就不相应了,可以参照上面的场景一(判断触摸点不再View B内,因此也不再检查View C. View A就被视作hit-test视图.)

比方说

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self isUserInteractionEnabled]) {
        return nil;
    } else {
        NSLog(@"%@---hitTest",[self class]);
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            NSLog(@"%@---wai",[subview class]);
            UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
            NSLog(@"%@---cycle",[self class]);
            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
}

点击了绿色和红色未重叠的那块,在紫色视图的hitTist方法里面,走到下面这步,

            UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];

然后会调用红色view的hitTest的方法里面,然后先判断的pointInside方法,点是不是在红色view的身上,没有,所以就返回nil,然后就不继续往下走了。

先说解决方法

在父视图重写下面的方法,也就是在红色view的类下重写这个方法

//重写该方法后可以让超出父视图范围的子视图响应事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    if (view == nil) {
        for (UIView *subView in self.subviews) {
            CGPoint convertPoint = [subView convertPoint:point fromView:self];
            if (CGRectContainsPoint(subView.bounds, convertPoint)) {
                view = subView;
            }
        }
    }
    return view;
}
// 将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;

// 将像素point从view中转换到当前视图中,返回在当前视图中的像素值
- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;
判断给定的点是否被一个CGRect包含,可以用CGRectContainsPoint函数

七:如何扩大button的点击区域

1:分类

@implementation UIButton (Extension)

@dynamic hitTestEdgeInsets;

static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
    if(value) {
        UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
    }else {
        return UIEdgeInsetsZero;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) ||       !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }
    
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    
    return CGRectContainsPoint(hitFrame, point);
}

@end
 UIButton *button = [[UIButton alloc] initWithFrame:CGRectZero];
//上下左右的各个方向的Insets量
[button setHitTestEdgeInsets:UIEdgeInsetsMake(-10, -10, -10, -10)];
[self.view addsubview:button];

2:上述链接可以优化的一点的是:不要重写UIButton类的pointInside:withEvent:方法,使用swizzle交换IMP

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSError *error = nil;
        [self jr_swizzleMethod:@selector(pointInside:withEvent:) withMethod:@selector(hitTest_pointInside:withEvent:) error:&error];
        NSAssert(!error, @"UIView+HitTest.h swizzling failed: error = %@", error);
    });
}

- (BOOL)hitTest_pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero)) {
        return [self hitTest_pointInside:point withEvent:event];
    }
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

两种方式,看君选择

八:view同时添加了tap手势也实现了touch方法,如何响应

view中给view添加了tapGesture方法,还重写了touch方法,那么两个都可以被调用

@implementation RedView

- (instancetype)init
{
    if (self == [super init]) {
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actions)];
        [self addGestureRecognizer:tap];
        
    }
    return self;
}

- (void)actions
{
    NSLog(@"red的actions方法");
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"red的touch方法");
}
2021-04-29 15:05:18.326728+0800 test[2866:977571] red的touch方法
2021-04-29 15:05:18.374361+0800 test[2866:977571] red的actions方法

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值