事件傳遞響應(yīng)鏈

一滋将、簡(jiǎn)介

在我們點(diǎn)擊屏幕的時(shí)候转绷,iOS系統(tǒng)會(huì)獲取“單擊”行為修然,把這個(gè)信息包裝成UITouch和UIEvent對(duì)象徊哑,然后在當(dāng)前運(yùn)行的程序匯總逐級(jí)查找這個(gè)事件的響應(yīng)對(duì)象袜刷,直到?jīng)]有響應(yīng)者響應(yīng),這個(gè)過(guò)程就是事件的響應(yīng)鏈莺丑。

事件傳遞過(guò)程如下圖:

事件傳遞響應(yīng)鏈

二著蟹、響應(yīng)者對(duì)象

iOS中要響應(yīng)事件都必須繼承UIResponder墩蔓,且是對(duì)象,我們稱之為響應(yīng)者對(duì)象萧豆。
繼承UIResponder的有:

  • UIApplication
  • UIViewController
  • UIView

2.1事件處理

UIResponder提供了以下方法處理用戶的事件奸披,分別對(duì)應(yīng)點(diǎn)擊,移動(dòng)涮雷,結(jié)束和取消阵面,其中取消方法只有在APP強(qiáng)制退出或者來(lái)電的時(shí)候才會(huì)被調(diào)用

  • 觸摸事件
//開始點(diǎn)擊
-(void)touchesBegin:(NSSet<UITouch *> *)touches withEvent(nullable UIEvent *)event;
//開始拖拽
-(void)touchedMoved:(NSSet<UITouch *>)touches withEvent:(nullable UIEvent *)evnet;
//點(diǎn)擊結(jié)束
-(void)touchedEnded:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;
//點(diǎn)擊取消
-(void)touchedCanceled:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;

NOTE:如果處理UIView的觸摸事件,必須要自定義UIView的子類洪鸭,在子類中重寫上面四個(gè)方法样刷。
如果是處理UIViewController的觸摸事件,直接重寫上面四個(gè)方法即可览爵。

  • 加速事件
//開始加速
-(void)motionBegin:(NSSet<UITouch *> *)touches withEvent(nullable UIEvent *)event;
//點(diǎn)擊加速
-(void)motionEnded:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;
//加速取消
-(void)motionCanceled:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;
  • 遠(yuǎn)程控制事件
-(void)remoteControlReceviedWithEvent:(UIEvent *)evnet;

2.2觸摸事件詳解

在UIView上我們可以重寫這幾個(gè)方法進(jìn)行觸摸事件的回調(diào)處理置鼻,這些方法中都接收兩個(gè)參數(shù):UITouch對(duì)象的集合(點(diǎn)擊對(duì)象),UIEvent對(duì)象(事件對(duì)象)蜓竹。

2.2.1事件對(duì)象UIEvent

UIEvent表示用戶交互對(duì)象箕母,用UIEventTpye類型的屬性可以表示單簽的響應(yīng)事件類型:

@property(nonatomic,readonly) UIEventType type

typedef NS_ENUM(NSInteger, UIEventType) {
    UIEventTypeTouches,//多點(diǎn)觸控
    UIEventTypeMotion, //移動(dòng)拖拽
    UIEventTypeRemoteControl,//遠(yuǎn)程操作
    UIEventTypePresses //3D touch
};

2.2.2點(diǎn)擊對(duì)象UITouch

對(duì)象:
  • UITouch表示單個(gè)點(diǎn)擊,當(dāng)用戶用手指觸摸屏幕時(shí)俱济,會(huì)創(chuàng)建一個(gè)或多個(gè)與手指相關(guān)的UITouch對(duì)象(一根手指對(duì)應(yīng)一個(gè)UITouch對(duì)象)嘶是。
  • 如果兩個(gè)手指同時(shí)觸摸一個(gè)view,那么view只會(huì)調(diào)用一次toucheBegan方法蛛碌,touches包含兩個(gè)UITouch對(duì)象俊啼。
  • 兩個(gè)手指一前一后點(diǎn)擊屏幕,會(huì)調(diào)用兩次左医,每次調(diào)用的方法里的touches包含一個(gè)UITouch對(duì)象授帕。
作用:
  • 保存跟手指相關(guān)的信息,比如觸摸的位置浮梢,時(shí)間跛十,階段
  • 當(dāng)手指移動(dòng)的時(shí)候,系統(tǒng)會(huì)更新改UITouch對(duì)象的信息秕硝。
  • 當(dāng)手指離開屏幕的時(shí)候芥映,系統(tǒng)會(huì)銷毀該UITouch對(duì)象。
UITouch屬性:
  • 當(dāng)前點(diǎn)擊狀態(tài)

用UITouchPhase屬性來(lái)表示當(dāng)前點(diǎn)擊的狀態(tài)远豺,這些狀態(tài)對(duì)應(yīng)奈偏,開始、移動(dòng)躯护、停止不動(dòng)惊来、結(jié)束、取消五個(gè)狀態(tài)

@property(nonatomic,readonly) UITouchPhase phase;

typedef NS_ENUM(UITouchPhase,UITouchPhase{
    NSTouchPhaseBegin,
    NSTouchPhaseMoved,
    NSTouchPhaseStationary,
    NSTouchPhaseEnded,
    NSTouchPhaseCanceled,
};
  • 觸摸產(chǎn)生時(shí)所處的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
  • 觸摸產(chǎn)生時(shí)所處的視圖
@property(nonatomic,readonly,retain) UIView *view
;
  • 點(diǎn)按屏幕的次數(shù)

可以根據(jù)tapCount判斷單擊棺滞、雙擊或更多的點(diǎn)擊

@property(nonatomic,readonly) NSUInteger tapCount;
  • 觸摸事件產(chǎn)生或變化時(shí)的時(shí)間裁蚁,單位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;
UITouch方法:
  • 通過(guò)以下方法可以獲取用戶點(diǎn)擊的位置:
//獲取點(diǎn)擊坐標(biāo)點(diǎn)
-(CGPoint) locationInView:(UIView *)view;
//獲取上個(gè)點(diǎn)擊位置的坐標(biāo)點(diǎn)
-(CGPoint) previousLocationInView:(UIView *)view;

2.3 代碼實(shí)現(xiàn)拖拽UIView

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //獲取當(dāng)前UITouch對(duì)象
    UITouch *touch = [touches anyObject];
    //當(dāng)前手指所指位置
    CGPoint currentP = [touch locationInView:self];
    //之前手指所在位置
    CGPoint previousP = [touch previousLocationInView:self];
    //相對(duì)于之前的位置x,y方向的偏移量
    CGFloat offsetX = currentP.x - previousP.x;
    CGFloat offsetY = currentP.y - previousP.y;
    //在之前的偏移的基礎(chǔ)上再做移動(dòng)
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}

三矢渊、事件的產(chǎn)生和傳遞

發(fā)生觸摸事件A后,系統(tǒng)會(huì)將A加到UIApplication管理的事件隊(duì)列中枉证,再由UIApplication把事件分發(fā)下去矮男,通常,會(huì)先把A事件發(fā)送給keyWindow室谚。keyWindow會(huì)在視圖層次中找出一個(gè)最適合的視圖來(lái)處理觸摸事件毡鉴,找到合適的視圖后,會(huì)調(diào)用該視圖的touched方法來(lái)做具體的操作秒赤。通常的順序?yàn)椋?/p>

UIApplicaton -> UIWindow -> UIViewControleler -> UIView -> subView

note:如果父視圖不能接受觸摸事件眨补,那么子視圖就不可能收到觸摸事件

3.1查找最合適的控件處理事件

  • 判斷keyWindow是否能接受觸摸事件
  • 判斷觸摸點(diǎn)是否在keyWindow的bounds里面
  • 子視圖數(shù)組(subviews)從后往前遍歷,也就是從視圖的頂部往底部遍歷倒脓,重復(fù)判斷:1撑螺、是否能接受觸摸事件(是否是UIResponder的子類)2、觸摸點(diǎn)是否在自己的bounds里面崎弃。
  • 如果找到了符合條件view1甘晤,就好把事件傳遞view1,再遍歷view1的子控件饲做,再次判斷线婚。
  • 如果沒有符合條件的子控件,那么自己就是最合適的控件盆均。

note:UIImageView的交互默認(rèn)是關(guān)閉的塞弊,如果要往上添加button或者手勢(shì)需要先將交互打開

圖示:


查找最佳響應(yīng)者

3.2兩個(gè)底層方法

在查找最合適的view的過(guò)程中用到了兩個(gè)最重要的方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

3.2.1 hitTest:withEvent:

只要事件一傳遞給一個(gè)控件,這個(gè)控件就會(huì)調(diào)用自己的hitTest:withEvent:方法泪姨,用于尋找并返回最合適的view游沿, 它不管這個(gè)控件能不能處理事件也不管點(diǎn)是否在view上,事件都會(huì)先傳給這view再調(diào)用這個(gè)view的hitTest方法肮砾。不管點(diǎn)擊哪里诀黍,最合適的view都是hitTest返回的那個(gè)view。

利用這個(gè)特性可以攔截事件的處理:
事件傳遞給誰(shuí)就會(huì)調(diào)用這個(gè)view的hitTest:withEvent:方法仗处,如果返回nil眯勾,那么該方法的控件本身和子控件不是最合適的view,那么最合適的view就是該控件的父控件婆誓。

** tip:想讓a成為最合適的view就重寫a的父控件b的hitTest:withEvent:方法吃环,或者自己的hitTest:withEvent:方法返回 self。建議采用第一種**

攔截代碼實(shí)現(xiàn):

如果沒有攔截最合適的view的時(shí)候洋幻,DView是可以拖動(dòng)的郁轻,但是攔截之后,由于最合適的view變成了BView鞋屈,所以DView的touched方法不會(huì)實(shí)現(xiàn)

#implementation Aview

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    for (UIView *v in self.subviews) {
        if ([v isKindOfClass:[BView class]]) {
            return v;
        }
    }
    return self;
}
特殊情況:
  • 誰(shuí)都不能處理事件范咨,窗口也不能處理故觅。

重寫window的hitTest:withEvent:方法return nil

  • 只能由窗口處理事件厂庇。

控制器的view的hitTest:withEvent:方法return nil或者window的hitTest:withEvent:方法return self

  • return nil的含義:

調(diào)用當(dāng)前hitTest:withEvent:方法的view不是合適的view渠啊,子控件也不是合適的view。如果同級(jí)的兄弟控件也沒有合適的view权旷,那么最合適的view就是父控件替蛉。

hitTest:withEvent:中return nil的意思是調(diào)用當(dāng)前hitTest:withEvent:方法的view不是合適的view,子控件也不是合適的view拄氯,那么就去判斷上一個(gè)調(diào)用hittest的兄弟控件是否有合適的view躲查,如果同級(jí)的兄弟控件也沒有合適的view,那么最合適的view就是父控件译柏。

3.2.2 pointInside:withEvent

pointInside:withEvent:方法判斷點(diǎn)在不在當(dāng)前view上(方法調(diào)用者的坐標(biāo)系上)如果返回YES镣煮,代表點(diǎn)在方法調(diào)用者的坐標(biāo)系上;返回NO代表點(diǎn)不在方法調(diào)用者的坐標(biāo)系上,那么方法調(diào)用者也就不能處理事件鄙麦。

3.3事件響應(yīng)的傳遞

事件響應(yīng)的傳遞(需要做的操作)與事件的傳遞(查找最佳響應(yīng)者)的過(guò)程相反:
view -> 父view -> viewController -> keyWindow -> application
用代碼驗(yàn)證:

#implementation Dview
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UIResponder *nextResponder = [self nextResponder];
    NSMutableString *prefix = @"".mutableCopy;
    while (nextResponder) {
        NSLog(@"%@%@\n",prefix,[nextResponder class]);
        [prefix stringByAppendingString:@"--"];
        nextResponder = [nextResponder nextResponder];
    }
     NSLog(@"ddd");
}
事件響應(yīng)傳遞
111.gif

從打印處理的信息可以看出事件響應(yīng)的傳遞為:

2016-03-09 17:30:41.092 responder[12747:3088146] CView
2016-03-09 17:30:41.092 responder[12747:3088146] AView
2016-03-09 17:30:41.093 responder[12747:3088146] UIView
2016-03-09 17:30:41.093 responder[12747:3088146] ViewController
2016-03-09 17:30:41.093 responder[12747:3088146] UIWindow
2016-03-09 17:30:41.093 responder[12747:3088146] UIApplication
2016-03-09 17:30:41.094 responder[12747:3088146] AppDelegate

3.4一個(gè)事件多個(gè)響應(yīng)

在DView的touched方法中做操作處理后調(diào)用super的touched方法

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

在CView的touched方法中做操作處理后調(diào)用super的touched方法

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

AView中以此類推!

閱讀原文

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末典唇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子胯府,更是在濱河造成了極大的恐慌介衔,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骂因,死亡現(xiàn)場(chǎng)離奇詭異炎咖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)寒波,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門乘盼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人俄烁,你說(shuō)我怎么就攤上這事蹦肴。” “怎么了猴娩?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵阴幌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我卷中,道長(zhǎng)矛双,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任蟆豫,我火速辦了婚禮议忽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘十减。我一直安慰自己栈幸,他們只是感情好愤估,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著速址,像睡著了一般玩焰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芍锚,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天昔园,我揣著相機(jī)與錄音,去河邊找鬼并炮。 笑死默刚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逃魄。 我是一名探鬼主播荤西,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伍俘!你這毒婦竟也來(lái)了邪锌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤养篓,失蹤者是張志新(化名)和其女友劉穎秃流,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柳弄,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舶胀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碧注。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚣伐。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖萍丐,靈堂內(nèi)的尸體忽然破棺而出轩端,到底是詐尸還是另有隱情,我是刑警寧澤逝变,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布基茵,位于F島的核電站,受9級(jí)特大地震影響壳影,放射性物質(zhì)發(fā)生泄漏拱层。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一宴咧、第九天 我趴在偏房一處隱蔽的房頂上張望根灯。 院中可真熱鬧,春花似錦、人聲如沸烙肺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)桃笙。三九已至氏堤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怎栽,已是汗流浹背丽猬。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工宿饱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熏瞄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓谬以,卻偏偏與公主長(zhǎng)得像强饮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子为黎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容