iOS 事件传递以及响应

一、事件传递



  • 1、事件传递

2寻找最合适的View.png

验证一下是不是这个流程!

  • 1、建立一个工程!搭建如下界面。


    图片

在修改View背景

blue

  • 2、建立一个自己的Window,并且在AppDelegate中替换为自己Window
//使用自己的Window
    self.window = [[PQWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    //加载界面
    UIStoryboard * board = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UIViewController * viewController = [board instantiateInitialViewController];
    self.window.rootViewController = viewController;
    [self.window makeKeyAndVisible];
  • 3、重写touchesbegan...方法:在包括Window和其他的View、VC
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    NSLog(@"%s",__func__);
}
  • 4、分别依次点击
  • blue view
2016-08-27 09:41:06.116 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
2016-08-27 09:41:06.116 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
  • green view
2016-08-27 09:42:22.891 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
2016-08-27 09:42:22.891 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
2016-08-27 09:42:22.891 PQHitTest[1647:58543] -[GreenView touchesBegan:withEvent:]
  • pink view
2016-08-27 09:43:08.274 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
2016-08-27 09:43:08.275 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
2016-08-27 09:43:08.275 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
  • yellow view
2016-08-27 09:43:47.010 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
2016-08-27 09:43:47.010 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
2016-08-27 09:43:47.011 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
2016-08-27 09:43:47.011 PQHitTest[1647:58543] -[YellowView touchesBegan:withEvent:]
  • brown view
2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
2016-08-27 09:44:37.802 PQHitTest[1647:58543] -[BrownView touchesBegan:withEvent:]
  • black view
2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[PinkView touchesBegan:withEvent:]
2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[BrownView touchesBegan:withEvent:]
2016-08-27 09:44:49.026 PQHitTest[1647:58543] -[BlackView touchesBegan:withEvent:]
  • orange view
2016-08-27 09:45:02.546 PQHitTest[1647:58543] -[PQWindow touchesBegan:withEvent:]
2016-08-27 09:45:02.546 PQHitTest[1647:58543] -[ViewController touchesBegan:withEvent:]
2016-08-27 09:45:02.546 PQHitTest[1647:58543] -[OrangeView touchesBegan:withEvent:]
  • 2、如何寻找最合适的View

  • 1、检查自己是能否接受触摸事件
  • 2、检测触摸点是否在自己身上
  • 3、从后往前遍历子控件,重复1 2
  • 4、如果没有符合的子控件,那么自己就是合适的View

eg:点击green View

window → blue view → orange view → pink view → green view

  • 1、首先会响应window的hitTest方法

  • 2、在响应blueView的hitTest方法,从后往前遍历,先找到blue view.subviews的最后一个View,也就是orange view

  • 3、判断点是不是在orange view上,不在的话返回nil,并且继续从blue view.subviews继续从后往前遍历

  • 4、判断点是不是在pink view上,不在的话返回nil,并且继续从blue view.subviews继续从后往前遍历

  • 5、当遍历的到green view时,发现点在green view上,然后继续遍历 green view.subviews,发现并没有子控件。

  • 6、这个时候就返回green view,从而使得 green view变成了最合适的View

使用程序验证:把touchesBegan...方法全部注释掉,并且在blue / green / pink / orange view以及window中添加下面方法

2016-08-27 10:36:23.747 PQHitTest[2603:89650] -[PQWindow hitTest:withEvent:]
2016-08-27 10:36:23.747 PQHitTest[2603:89650] -[OrangeView hitTest:withEvent:]
2016-08-27 10:36:23.747 PQHitTest[2603:89650] -[PinkView hitTest:withEvent:]
2016-08-27 10:36:23.748 PQHitTest[2603:89650] -[GreenView hitTest:withEvent:]

通过输出我们可以看到,系统查找过程确实和我们预想的一样。



eg:点击black View:把所有的View都添加hitTest方法

window → blue view → orange view → pink view → brown view → black view


2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[PQWindow hitTest:withEvent:]
2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[OrangeView hitTest:withEvent:]
2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[PinkView hitTest:withEvent:]
2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[BrownView hitTest:withEvent:]
2016-08-27 10:42:56.865 PQHitTest[2818:95834] -[BlackView hitTest:withEvent:




二、事件响应



1事件响应.png

发生触摸事件后,系统会自动将该事件加入到一个有UIApplication管理的事件队列中,UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序的主窗口。
在主窗口的视图中会找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。
当找到后,则会调用touches中的:

  • touchesBegan...
  • touchesMoved...
  • touchesEnded...

1、先把所有的hitTest方法给注释掉!

然后开启VC中和WIndows中的touchesBegan方法,运行程序

2016-08-27 10:49:32.998 PQHitTest[2972:100633] -[PQWindow touchesBegan:withEvent:]
2016-08-27 10:49:32.999 PQHitTest[2972:100633] -[ViewController touchesBegan:withEvent:]

如果没有把其他的View的touchesbegan注释掉,则会依次找到View的touchesBegan方法。




三、练习



1、不管点击那个View,只响应blue view的touchesBegan...

解析:

现在我们知道hitTest方法就是用来查找最合适的View的,并且需要一个返回值,如果返回nil:表示不是合适的View,返回不为空:表示是合适的View。
通过这个一个特性我们可以把BaseView中的hitTest方法中直接返回nil

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    
    return nil;
}




2、不管点击那个View,只响应green view的touchesBegan...

解析:

hitTest方法是从后往前遍历子控件,所以我们把pink/orange view中的hitTest方法返回nil,把green view中的hitTest方法返回self来达到效果,这里要记得把BaseView中的hitTest方法注释或者super,把VC中的touchesBegan..注释掉,打开green view中的touchesBegan...

// pink and orange
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    
    return nil;
}
//green 
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    
    return self;
}



打印结果:

2016-08-27 11:09:59.757 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
2016-08-27 11:10:00.052 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
2016-08-27 11:10:00.268 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
2016-08-27 11:10:00.444 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
2016-08-27 11:10:00.660 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]
2016-08-27 11:10:00.876 PQHitTest[3437:115811] -[GreenView touchesBegan:withEvent:]


3、添加一个导航栏,然后在工程中新建一个VC,搭建如下界面

搭建界面2



然后

搭建界面1

要求:点击红色按钮是响应红色按钮点击事件

  • 3.1在然后创建一个button class、一个View class
  • 3.2并且连接好
  • 3.3添加touchesBegan方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    NSLog(@"%s",__func__);
}
  • 3.4运行后发现只要是点击橙色View,哪怕点同时也在按钮上,并不会触发按钮事件。
如何达到这一效果?

1、先明白为什么不会触发按钮事件:

因为在寻找最合适的View的时候是从后往前遍历
Application → window → VC.view → 橙色View
这个时候不会继续去寻找了,因为橙色已经满足了条件。

所以我要在橙色的hitTest方法中动手脚。



2、如何动手脚

思想:当你在查找最合适的View的时候是根据point点去判断的,我只需要判断当你这个点,恰好也在我的Button上的时候,我就返回button,而不是返回橙色View
需要用到的方法:

  • convertPoint:toView: 把点转化为View中的点
  • pointInside:withEvent: 用与判断点在不在VIew上
#import "PQViewTwo.h"
#import "PQButtonTwo.h"
@interface PQViewTwo ()

@property (nonatomic,weak) IBOutlet PQButtonTwo *button;

@end

@implementation PQViewTwo

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    CGPoint p = [self convertPoint:point toView:self.button];
        
    if ([self pointInside:p withEvent:event]) {
        return self.button;
    }
    return [super hitTest:point withEvent:event];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    NSLog(@"%s",__func__);
}

@end


4、添加一个导航栏,然后在工程中新建一个VC,搭建如下界面

界面效果



然后新建一个类,和button关联

关联后


最终效果:
最终效果


1、重写touchesBegan...touchesMoved....方法完成拖动效果

#import "ThreeButton.h"



@implementation ThreeButton{
    CGPoint _startP;
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UITouch * touce = [touches anyObject];
    _startP = [touce locationInView:self];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UITouch * touce = [touches anyObject];
    CGPoint curP = [touce locationInView:self];
    
    CGFloat x = self.frame.origin.x + curP.x - _startP.x;
    CGFloat y = self.frame.origin.y + curP.y - _startP.y;
    
    self.frame = CGRectMake(x, y, self.frame.size.width, self.frame.size.height);
}


@end


2、添加一个超出button大小范围的View

- (void)awakeFromNib{
    UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(80, -200, 200, 200);
    [button setImage:[UIImage imageNamed:@"222"] forState:UIControlStateNormal];
    [button setImage:[UIImage imageNamed:@"bc"] forState:UIControlStateHighlighted];
    [self addSubview:button];
}

3、此时运行程序如下:

效果图1

鼠标点击无效:并没有切换图片


log打印如下

2016-08-27 14:30:58.386 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:30:58.809 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:30:59.050 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:30:59.282 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:30:59.521 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:30:59.721 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:30:59.906 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:31:00.154 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]
2016-08-27 14:31:00.377 PQHitTest[6501:209375] -[ThreeViewController touchesBegan:withEvent:]


4、修改hitTest方法

  • 1、先把点转化
  • 2、判断点在不在图片按钮上
  • 3、做出对应处理
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    CGPoint isSub = [self convertPoint:point toView:self.subviews[0]];
    if ([self.subviews[0] pointInside:isSub withEvent:event]) {
        return self.subviews[0];
    }else {
        return [super hitTest:point withEvent:event];
    }
}


5、最终效果

最终效果

四、hitTest实现原理:

  • 1、检查自己是能否接受触摸事件
  • 2、检测触摸点是否在自己身上
  • 3、从后往前遍历子控件,重复1 2
  • 4、如果没有符合的子控件,那么自己就是合适的View

实现代码

重写BaseView的hitTest方法,如果能准确的找到子控件(最合适的View),那么久是正确的代码
//实现原理
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//   1、检查自己是能否接受触摸事件
    //不接受事件或者隐藏或者透明,都返回nil,不是最合适的View
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha == 0) {
        return nil;
    }
//   2、检测触摸点是否在自己身上
    //如果点不在控件上,返回nil
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
//   3、从后往前遍历子控件,重复1 2
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView * view = self.subviews[i];
        CGPoint viewP = [self convertPoint:point toView:view];
        
        if ([view hitTest:viewP withEvent:event]) {
            NSLog(@"%@",view);
            return view;
        }
    }
    
//   4、如果没有符合的子控件,那么自己就是合适的View
    NSLog(@"%@",self);
    return self;
    
}

demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容