iOS 事件处理总结与思考

UIControl

UIControl继承自UIView。
UIControl 依赖于Target-Action设计模式。即当发生一个事件时,UIControl会调用sendAction:to:forEvent:方法来将行为消息发送到UIApplication对象,再由UIApplication对象调用其sendAction:to:fromSender:forEvent:方法来将消息分发到指定的target上。如果没有指定target,则会将事件分发到响应链上第一个想处理该消息的对象上。

UIControl有不同的状态

typedef NS_OPTIONS(NSUInteger, UIControlState) {
    UIControlStateNormal       = 0,
    UIControlStateHighlighted  = 1 << 0,                  // used when UIControl isHighlighted is set
    UIControlStateDisabled     = 1 << 1,
    UIControlStateSelected     = 1 << 2,                  // flag usable by app (see below)
    UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
    UIControlStateApplication  = 0x00FF0000,              // additional flags available for application use
    UIControlStateReserved     = 0xFF000000               // flags reserved for internal framework use
};

通过继承UIControl类,就可以使用OC内建的 target-action 机制以及简化版的 event-handling。主要有以下两类方法来实现UIControl。

  1. 重写 sendAction:to:forEvent:方法。这样就可以观察或者改写OC的分发机制,从而达到监听某个特定的对象(object)对于特定的事件(event)做了什么特定的处理(selector)。进一步的可以拦截到这些对象的事件,把它们发送到其他对象,或者让本对象执行其他的方法。

  2. 重写

    beginTrackingWithTouch:withEvent:, 
        
    continueTrackingWithTouch:withEvent:, 
        
    endTrackingWithTouch:withEvent:, 
          
    cancelTrackingWithEvent: 
        

等方法。这样就可以追踪并获取到control对象的状态。进一步的,可以依据这些状态去更新页面上控件的状态;或者调用某些方法,执行其他命令。

此处需要注意,苹果文档上有一句:Always use these methods to track touch events instead of the methods defined by the UIResponder class.不知为何,苹果要这样写。

UIResponder

UIResponder对象及其子类的对象都叫做响应者。继承关系如下图:


UIResponder.png

也就是说UIApplication,UIViewCOntroller,UIView都是响应者,都可以接收并处理事件。
响应者是响应事件的。在iOS中,事件分为三种。即触摸事件,加速计事件,远程控制事件。
一般开发中触摸事件使用最频繁,而且其他两种事件处理方式与触摸事件大同小异,所以只介绍触摸事件。

触摸事件包括:

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

可以看出,触摸事件包括用户交互的整个过程。包括触摸开始,用户滑动,触摸结束,以及触摸因为其他事件(比如来电话)被取消。

以上方法中都包含两个参数:touches和event。
touches是一个包含UITouch对象的集合。

UITouch对象记录着某个事件中手指的相关信息,比如位置,大小,运动状况,手指在屏幕上的压力(限于有3D Touch的手机)等。
主要属性有:

  1. window 触摸所在的窗口
  2. view 触摸所在的视图
  3. tapCount 点击屏幕的次数
  4. majorRadius 触摸范围半径。锤子科技的Big-Bang就是根据触摸半径的大小来判断是否“炸开”文字的。
  5. gestureRecognizers 手势数组。如果触摸事件是发生在view对象上的,给这个view对象添加的手势UIGestureRecognizer都会在这个数组中。如果view对象没有添加过手势,这个数组中也有一个系统手势:_UISystemGestureGateGestureRecognizer。

UIGestureRecognizer

手势识别。苹果文档中有这样一段描述值得注意:

A window delivers touch events to a gesture recognizer before it delivers them to the hit-tested view attached to the gesture recognizer. Generally, if a gesture recognizer analyzes the stream of touches in a multi-touch sequence and doesn’t recognize its gesture, the view receives the full complement of touches. If a gesture recognizer recognizes its gesture, the remaining touches for the view are cancelled. The usual sequence of actions in gesture recognition follows a path determined by default values of the cancelsTouchesInView, delaysTouchesBegan, delaysTouchesEnded properties:

即,当触摸事件发生时,如果,手势对象会先于view对象获取到触摸事件。如果这个手势对象可以处理该事件,那么view对象就不会接收到触摸事件。如果还想让view也接收到事件,就要把手势的cancelsTouchesInView属性设置为NO。
具体参见以下代码:

//给一个view对象添加UIButton子控件。
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(10, 20, 200, 20)];
[btn setTitle:@"button" forState:UIControlStateNormal];
//btn对象添加 target-action
[btn addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
UITapGestureRecognizer *btnTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(buttonTapAction)];
//设置cancelsTouchesInView属性。
btnTap.cancelsTouchesInView = NO;
[btn addGestureRecognizer:btnTap];

以上代码,当cancelsTouchesInView为YES时。只响应buttonTapAction方法;当为NO时,事件可以继续传递,buttonTapActionbuttonAction方法均响应。

UIGestureRecognizer 响应始终在主线程。测试代码中曾把添加手势的代码放在子线程中,结果发现手势的响应仍然是在主线程。我猜测是这样的,手势的添加不在乎在哪个线程,只要把手势添加到view上即可。触摸事件的发生以及传递是在主线程的。所以,我们的响应方法最终在主线程被执行。

但是,文档中有一句我不是很明白A gesture recognizer doesn’t participate in the view’s responder chain.查了一些资料,还是没有头绪,这个问题先记着,后续处理。//TODO:find the answer.

事件传递

问题来了,如果UIResponder,UIGestureRecognizer,UIControl各自的对象同时出现一个或者多个;又或者他们三个中不同的对象同时出现,那响应顺序是什么样子的呢?

  1. 上面分析过,UIGestureRecognizer和UIControl同时存在时。会优先处理UIGestureRecognizer,如果事件能够响应,则不再处理UIControl。
  2. 如果view对象在添加了UIGestureRecognizer手势的同时,也实现了UIResponder的方法,比如touchBegin。那响应顺序如何?以下是我的测试:
    如下图的结构:
UITestView.png

UITestViewB和UITestViewA都是UIView的子类。并且都添加了单击手势
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
同时,实现了- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event方法
我测试的结果是先响应UIResponder方法,再响应UIGestureRecognizer。

另外,如果想要UIResponder继续传递,那就直接调用super方法,触摸事件就可以接着传递给父控件;
如果想要UIGestureRecognizer继续传递,那就重写可以同时响应的代理方法--gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer。虽然这个方法的说法是可以让一个对象同时响应多个手势,但经过测试发现,当这个方法返回YES时,父子控件可以同时响应一个触摸事件。如果父子控件的这个代理方法都返回NO(或者不写,默认是NO),那么只有子控件响应触摸事件。

这里我还有一个问题,没有解决。就是UIControl了类的点击事件如何继续传递。情景是这样的。一个view中添加一个button子控件。butoon通过addTarget添加target-action。如何在点击button后,让该点击事件继续传递到父控件view上。view实现了toucheBegin并且也添加了tap手势。

我能想到的方法是,给button再次添加target-action。即在点击button是发出给两个target发送message,其中一个message是view。即把触摸事件发送给view。但是这样只能够实现相同的效果,并不是把同一个触摸事件传递给view。虽然应该不会有这样的需求,但我只是好奇这能够实现吗?

参考:

UIControl

UIControl补充

Target-Action

UIGestureRecognizer

EventHandlingiPhoneOS

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

推荐阅读更多精彩内容