Touch Events and UIResponder

Touch Events and UIResponder

Touch Events

做为UIResponder的子类,UIView可以重写以下四个触摸事件:

Event Description
- (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 系统事件,比如正在触摸时来电话了,触摸被取消

一个手指触摸屏幕,一个UITouch实例被创建,被触摸的view收到touchesBegan:withEvent:消息,UITouch实例在NSSet集合中。

当手指在屏幕上移动,UITouch实例被更新,包含了手指当前的位置信息,然后同一个view(和触摸事件开始的view)收到touchesMoved:withEvent:消息,此时的NSSet中包含同一个UITouch实例(和手指刚触摸屏幕时的UITouch是一个实例,只不过有了最新的位置信息)。

当手指离开屏幕,UITouch实例被更新为手指最后的位置,然后同一个view(和触摸开始时的view是一个)收到touchesEnded:withEvent:消息,当此方法执行结束,UITouch实例被销毁。

得出以下结论:

  • 一个UITouch实例对应触摸在屏幕上的一根手指,只要手指还触摸在屏幕上,这个实例就存在,并且包含手指当前的位置信息。
  • 触摸开始时被触摸的view,会收到所有的触摸事件,如果手指触摸到了这个view's frame的外面,那么这个view仍然会收到touchesMoved:withEvent:和touchesEnded:withEvent:消息。
  • 你不必去保持一个UITouch对象的引用,当UITouch状态发生变成,应用会让你访问到此对象。

每当触摸事件发生,会被添加到一个由UIApplication维护的队列中,实际上这个队列很少被充满,事件通常被立即触发。

Handling multiple touches

You can simulate multiple fingers on the simulator by holding down the option key as you drag.

#import "BKDrawView.h"
#import "BKLine.h"

@interface BKDrawView ()

//@property (nonatomic, strong) BKLine *currentLine;
// 保存当前的多个line
@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
@property (nonatomic, strong) NSMutableArray *finishedLines;

@end

@implementation BKDrawView

- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    
    if (self) {
        self.linesInProgress = [[NSMutableDictionary alloc] init];
        self.finishedLines = [[NSMutableArray alloc] init];
        self.backgroundColor = [UIColor grayColor];
        // 启用multiple touches
        self.multipleTouchEnabled = YES;
    }
    return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{

    
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    for (UITouch *t in touches) {
        CGPoint location = [t locationInView:self];
        
        BKLine *line = [[BKLine alloc] init];
        line.begin = location;
        line.end = location;
        
        // key为UITouch对象的内存地址
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        self.linesInProgress[key] = line;
        
    }
    
    
//    UITouch *t = [touches anyObject];
//    CGPoint location = [t locationInView:self];
//    
//    self.currentLine = [[BKLine alloc] init];
//    self.currentLine.begin = location;
//    self.currentLine.end = location;
    
    [self setNeedsDisplay];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    
    NSLog(@"%@", NSStringFromSelector(_cmd));
    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        BKLine *line = self.linesInProgress[key];
        line.end = [t locationInView:self];
    }
    
//    UITouch *t = [touches anyObject];
//    
//    CGPoint location = [t locationInView:self];
//    self.currentLine.end = location;
    
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        BKLine *line = self.linesInProgress[key];
        
        [self.finishedLines addObject:line];
        [self.linesInProgress removeObjectForKey:key];
    }
    
//    [self.finishedLines addObject:self.currentLine];
//    self.currentLine = nil;
    
    [self setNeedsDisplay];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"%@", NSStringFromSelector(_cmd));

    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        [self.linesInProgress removeObjectForKey:key];
    }
    [self setNeedsDisplay];
}

- (void)strokeLine:(BKLine *)line{
    UIBezierPath *bp = [UIBezierPath bezierPath];
    bp.lineWidth = 10;
    bp.lineCapStyle = kCGLineCapRound;
    
    [bp moveToPoint:line.begin];
    [bp addLineToPoint:line.end];
    [bp stroke];
}

- (void)drawRect:(CGRect)rect{
    [[UIColor blackColor] set];
    for (BKLine *line in self.finishedLines){
        [self strokeLine:line];
    }
    
    [[UIColor redColor] set];
    for (NSValue *key in self.linesInProgress) {
        [self strokeLine:self.linesInProgress[key]];
    }
    
//    if(self.currentLine){
//        [[UIColor redColor] set];
//        [self strokeLine:self.currentLine];
//    }
}

@end

Responder Chain

第七章提到过UIResponder和first responder,UIView, UIViewController, UIApplication, UIWindow都是UIResponder的子类。UIResponder可以处理触摸事件,view controller不是view,不会被直接触摸,但是通过responder chain他也可以处理触摸事件。

每个UIResponder有一个指针nextResponder,指向下一个responder,形成responder chain。
view的nextResponder通常是其view controller,如果没有view controller则是其superview。view controller的nextResponder通常是view的superview,最顶层的superview是UIWindow,window的nextResponder是单例的UIApplication对象。


如果一个responder不处理事件,则next responder会尝试处理,如果最后一个responder(application)也不处理,则事件被丢弃。

你可以明确地向next responder发送消息,现在假如next responder需要处理双击事件,代码如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    if (touch.tapCount == 2) { //双击
        [[self nextResponder] touchesBegan:touches withEvent:event];
        return;
    }
    // Go on to handle touches that are not double taps
}

UIControl

UIControl是UIButton和UISlider的父类,之前已经学过如何为其设置target 和 action,现在看下UIControl如何重写UIResponder的方法。

在UIControl中,每个可能的事件都被定义成常量,比如UIControlEventTouchUpInside事件,如果target被注册了此事件,则当此事件被触发时,target会收到action消息。

还可以让一个action处理多个event:

[rButton addTarget:tempController
    action:@selector(resetTemperature:)
    forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];

下面为UIControl重写UIResponder的方法:

// Not the exact code. There is a bit more going on!
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    // Reference to the touch that is ending
    UITouch *touch = [touches anyObject];
    // Location of that point in this control's coordinate system
    CGPoint touchLocation = [touch locationInView:self];
    // Is that point still in my viewing bounds?
    if (CGRectContainsPoint(self.bounds, touchLocation))
    {
        // Send out action messages to all targets registered for this event!
        [self sendActionsForControlEvents:UIControlEventTouchUpInside];
    } else {
        // The touch ended outside the bounds, different control event
        [self sendActionsForControlEvents:UIControlEventTouchUpOutside];
    }
}

At the end of the UIResponder method implementations, the control sends the message sendActionsForControlEvents: to itself. This method looks at all of the target-action pairs the control has, and if any of them are registered for the control event passed as the argument, those targets are sent an action message.
However, a control never sends a message directly to its targets. Instead, it routes these messages through the UIApplication object. Why not have controls send the action messages directly to the targets? Controls can also have nil-targeted actions. If a UIControl’s target is nil, the UIApplication finds the first responder of its UIWindow and sends the action message to it.

当调用sendActionsForControlEvents:方法,会查找control的target-action,如果有action被注册为响应指定的event,那么该action被执行。


本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十二章的总结。

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

推荐阅读更多精彩内容

  • 好奇触摸事件是如何从屏幕转移到APP内的?困惑于Cell怎么突然不能点击了?纠结于如何实现这个奇葩响应需求?亦或是...
    Lotheve阅读 56,665评论 51 597
  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 5,989评论 4 26
  • 7、不使用IB是,下面这样做有什么问题? 6、请说说Layer和View的关系,以及你是如何使用它们的。 1.首先...
    AlanGe阅读 656评论 0 1
  • iOS中有三类事件:UIEventTypeTouches触摸事件、 UIEventTypeMotion “动作”事...
    WeiHing阅读 36,963评论 7 69
  • 乔帮主在发布会上提到,用户的手才是最好的输入设备,的确,iPhone之后,非触屏手机再已难觅。触摸是最基本的用户输...
    皮皮Warrior阅读 2,215评论 3 48