事件傳遞

iOS的事件可以分為三類:觸摸事件,加速計(jì)事件,遠(yuǎn)程控制事件

iOS中不是任何對(duì)象都能處理對(duì)象,只有繼承了UIResponder的對(duì)象才能接收并處理事件. ---->響應(yīng)者對(duì)象

我們可以觀察到 UIView就是繼承于UIResponder,所以所有可看到的控件都是可以接收到事件的.

UIApplication,UIViewController,UIView都繼承自UIResponder,因此它們都是響應(yīng)者對(duì)象, 都能夠就收并處理事件

UIResponder內(nèi)部提供了以下方法專門用來事件的處理

1.觸摸事件:
--->我們經(jīng)常用來測試用的方法:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event觸摸事件開始
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event拖拽事件
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event觸摸結(jié)束事件
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event觸摸事件被打斷

2.加速計(jì)事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event 加速計(jì)事件開始
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event 加速計(jì)事件結(jié)束
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event 加速計(jì)事件被打斷

3.遠(yuǎn)程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event 接收遠(yuǎn)程控制事件

UIView的觸摸事件處理

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event觸摸事件開始[一根或者多根手指開始觸摸view,系統(tǒng)會(huì)自動(dòng)調(diào)用]
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event拖拽事件[在view上移動(dòng)的時(shí)候,調(diào)用這方法]
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event觸摸結(jié)束事件[手指離開view,調(diào)用這個(gè)方法]
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event觸摸事件被打斷[在觸摸結(jié)束前,被系統(tǒng)事件打斷,(如打電話,短信等等),系統(tǒng)自動(dòng)調(diào)用]

注意:touches中存放的都是:UITouch對(duì)象.

UITouch的屬性:

1.觸摸產(chǎn)生時(shí)所處的窗口
@property(nonatomic,readonly,retain)UIWindow *window;
2.觸摸產(chǎn)生時(shí)所處的視圖
@property(nonatomic,readonly,retain)UIView *view;
3.短時(shí)間內(nèi)點(diǎn)按屏幕的次數(shù),可以根據(jù)tapCount判斷單擊驰徊、雙擊或更多的點(diǎn)擊[一般不常用]
@property(nonatomic,readonly)NSUInteger tapCount;
4.記錄了觸摸事件產(chǎn)生或變化時(shí)的時(shí)間笤闯,單位是秒
@property(nonatomic,readonly)NSTimeInterval timestamp;
5.當(dāng)前觸摸事件所處的狀態(tài)
@property(nonatomic,readonly)UITouchPhase phase;

UITouch的方法

- (CGPoint)locationInView:(UIView *)view
1.返回值表示觸摸在view的位置
2.如果傳入的view為nil的話,返回的觸摸點(diǎn)在UIWindow的位置

- (CGPoint)previousLocationInView:(UIView *)view
記錄前一個(gè)觸摸點(diǎn)的位置

代碼實(shí)現(xiàn)簡單的拖拽,來解釋這個(gè)屬性和方法

// 驗(yàn)證點(diǎn)擊事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    
    NSLog(@"%s",__func__);
}
// 驗(yàn)證拖拽移動(dòng)事件
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch =  [touches anyObject];
    // 得到當(dāng)前點(diǎn)按的位置,是針對(duì)與self(紅色的view)來說的
    CGPoint curP = [touch locationInView:self];
    // 得到前一次點(diǎn)得位置
    CGPoint lastP = [touch previousLocationInView:self];
//    NSLog(@"%s",__func__);
//    NSLog(@"%@",touch);
    NSLog(@"%@-----%@",NSStringFromCGPoint(curP),NSStringFromCGPoint(lastP));
    // 計(jì)算 x軸方向的偏移量(都是針對(duì)于上一次棍厂,也就是針對(duì)與上一次的改變)
    CGFloat offsetX = curP.x - lastP.x;
    // 計(jì)算 y軸的偏移量
    CGFloat offsetY = curP.y - lastP.y;
    // 在原有的基礎(chǔ)上進(jìn)行平移
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}

// 點(diǎn)擊結(jié)束后調(diào)用
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{

    NSLog(@"%s",__func__);
}
// 程序被迫中斷時(shí)調(diào)用颗味,如電話打進(jìn)了。
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{

    NSLog(@"%s",__func__);
}

UIEvent

每產(chǎn)生一個(gè)事件,就會(huì)產(chǎn)生一個(gè)UIEvent對(duì)象, 內(nèi)部記錄著事件產(chǎn)生的時(shí)刻和類型

常見屬性

1.事件類型
@property(nonatomic,readonly)UIEventType type;
@property(nonatomic,readonly)UIEventSubtype subtype;
2.事件產(chǎn)生的時(shí)間
@property(nonatomic,readonly)NSTimeInterval timestamp;
lUIEvent還提供了相應(yīng)的方法可以獲得在某個(gè)view上面的觸摸對(duì)象(UITouch)

touches和event參數(shù)

一次完整的觸摸過程牺弹,會(huì)經(jīng)歷3個(gè)狀態(tài):
1觸摸開始:
- (void)touchesBegan:(NSSet *)touches withEvent (UIEvent *)event
2.觸摸移動(dòng):
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
3.觸摸結(jié)束:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

4.觸摸取消(可能會(huì)經(jīng)歷):
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

4個(gè)觸摸事件處理方法中浦马,都有NSSet *touches和UIEvent *event兩個(gè)參數(shù)

5.一次完整的觸摸過程中,只會(huì)產(chǎn)生一個(gè)事件對(duì)象例驹,4個(gè)觸摸方法都是同一個(gè)event參數(shù)

5.1如果兩根手指同時(shí)觸摸一個(gè)view捐韩,那么view只會(huì)調(diào)用一次touchesBegan:withEvent:方法退唠,touches參數(shù)中裝著2個(gè)UITouch對(duì)象
5.2如果這兩根手指一前一后分開觸摸同一個(gè)view鹃锈,那么view會(huì)分別調(diào)用2次touchesBegan:withEvent:方法,并且每次調(diào)用時(shí)的touches參數(shù)中只包含一個(gè)UITouch對(duì)象
5.3根據(jù)touches中UITouch的個(gè)數(shù)可以判斷出是單點(diǎn)觸摸還是多點(diǎn)觸摸

事件的產(chǎn)生和傳遞

1.發(fā)生觸摸事件后,系統(tǒng)會(huì)將該事件加入一個(gè)UIApplication管理的事件隊(duì)列中
2.UIApplication會(huì)從事件隊(duì)列中取出最前面的事件,并將事件分發(fā)下去進(jìn)行處理.通常先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)
3.主窗口會(huì)在視圖層次結(jié)構(gòu)中找到一個(gè)最合適的視圖來處理觸摸事件., 這也是整個(gè)事件處理過程中的第一步,也是最重要的一部
4.找到合適的視圖控件后,就會(huì)調(diào)用視圖控件的touches方法來作具體的事件處理

5.如果父控件不能接收觸摸事件瞧预,那么子控件就不可能接收到觸摸事件

那么如何找到最合適的控件來處理事件?
1.先查看自己是否能接收觸摸事件
2.觸摸點(diǎn)是否在自己身上
3.從后往前遍歷子控件,重復(fù)1,2步驟
4.如果沒有符合條件的子控件,那么就是自己最適合處理

圖形說明:


Snip20151007_3.png

觸摸事件的傳遞;
1.點(diǎn)擊了綠色的view
UIApplication-->UIWidow(keyWindow)-->白色的view-->綠色
2.點(diǎn)擊了藍(lán)色view
UIApplication-->UIWindow-->白色-->橙色-->藍(lán)色
3.點(diǎn)擊了黃色view
UIApplication-->UIWindow-->白色-->橙色-->藍(lán)色-->黃色

UIView不接收觸摸事件的三種情況:

1.不接收用戶交互:
userInteractionEnabled = NO
2.隱藏
hidden = YES
3.透明度(近乎透明)
alpha = 0.0~0.01之間
提示: UIImageView的userInteractionEnabled默認(rèn)為NO,因此UIImageView以及它的子控件默認(rèn)是不接收觸摸事件的

模擬蘋果來完成它的底部實(shí)現(xiàn)過程,

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

    NSLog(@"%@----%s",[self class],__func__);

}

// ponit是方法調(diào)用者坐標(biāo)系上的觸摸點(diǎn)位置--->當(dāng)觸摸事件產(chǎn)生時(shí),會(huì)調(diào)用這個(gè)方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.判斷能否接收觸摸事件 (繼承與uirespond)(隱藏屎债,交互仅政,透明度)
    if (self.hidden == YES || self.userInteractionEnabled == NO || self.alpha < 0.01 )
        return nil;
    
    // 觸摸點(diǎn)的位置在不在控件上
    if (![self pointInside:point withEvent:event]) return nil;
    
    // 如果在控件上,則遍歷控件的子控件(由后往前)
    int count = (int)self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
        // 取出最前面的子控件
        UIView *childView = self.subviews[i];
        // 將觸摸點(diǎn)的位置盆驹,轉(zhuǎn)換成該控件的坐標(biāo)系上的點(diǎn)
        CGPoint childP = [self convertPoint:point toView:childView];
        
        // 用點(diǎn)的位置圆丹,以及事件再次判斷
        UIView *fitView = [childView hitTest:childP withEvent:event];
        // 如果找到合適的view,則返回合適的view
        if (fitView) {
            return fitView;
        }
        
    }
    // 沒有合適的view 則返回自己躯喇。
    return self;
}
點(diǎn)擊事件.gif

控制臺(tái)的打印


Snip20151007_5.png

既然已經(jīng)知道了,事件的傳遞過程,那么我們完全可能做出一些特殊的行為,去攔截操作, 一下兩個(gè)方法都可以進(jìn)行事件的攔截

// 判斷觸摸點(diǎn)在view上,是否能夠接收事件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // 將點(diǎn)得坐標(biāo)系改變
    CGPoint blueP = [self convertPoint:point toView:_blueBtn];
    // 判斷是否在藍(lán)色按鈕上 辫封,
    if ([self.blueBtn pointInside:blueP withEvent:event]) {
    // 如果在,則不讓它進(jìn)行相應(yīng)廉丽。
        return NO;
    }   
    // 判斷繼續(xù)(由系統(tǒng)自行判斷)
    return [super pointInside:point withEvent:event];
}

// 重寫hittest 方法倦微,可以攔截監(jiān)聽。 或者說正压,想讓誰聽欣福,誰就聽
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 將點(diǎn)得坐標(biāo)系改變
    CGPoint blueP = [self convertPoint:point toView:_blueBtn];
   // 判斷是否在藍(lán)色按鈕上 ,
    if ([self.blueBtn pointInside:blueP withEvent:event]) {
        return _blueBtn;
    }
    // 繼續(xù)判斷(由系統(tǒng)內(nèi)部做決定)
    return [super hitTest:point withEvent:
            event];
    //return self; 不能返回self焦履,這樣會(huì)造成拓劝,無論條件是否符合(能不能監(jiān)聽,點(diǎn)有沒有在它上面)嘉裤,點(diǎn)都在yellow上
}
Snip20151007_6.png

點(diǎn)擊攔截.gif

層級(jí)結(jié)構(gòu)就是黃色的view藍(lán)色的按鈕上面
按照,事件傳遞的規(guī)律,點(diǎn)擊黃色區(qū)域應(yīng)該是view做出響應(yīng),當(dāng)然點(diǎn)擊覆蓋在按鈕上的區(qū)域,也應(yīng)該是view作出相應(yīng),但是由于我們將事件傳遞做出了改變,所以發(fā)生了事件攔截現(xiàn)象. 按鈕能夠監(jiān)聽點(diǎn)擊事件

觸摸事件處理的詳細(xì)過程

1.用戶點(diǎn)擊了屏幕后產(chǎn)生一個(gè)觸摸事件,經(jīng)過一系列傳遞以后,會(huì)找到最合適的視圖控件來處理這個(gè)事件
2.找到最合適的視圖空間后,就會(huì)調(diào)用控件的touches方法來做具體事件處理
3.這些touches方法的默認(rèn)做法是將事件順著響應(yīng)鏈條向上傳遞,將事件交給上一個(gè)響應(yīng)者進(jìn)行處理

響應(yīng)者鏈條示意圖

1.響應(yīng)者鏈條:是由多個(gè)響應(yīng)者對(duì)象連接起來的鏈條
2.作用:能很清楚的看見每個(gè)響應(yīng)者之間的對(duì)象,并且可以讓一個(gè)事件多個(gè)對(duì)象處理
3.響應(yīng)者對(duì)象:能處理事件的對(duì)象


Snip20151007_4.png

事件傳遞的完整過程

1.先將事件對(duì)象由上往下傳遞(由父控件傳遞給子控件),找到最合適的控件來處理這個(gè)事件
2.調(diào)用最合適控件的touches方法
3.如果調(diào)用了[super touches...];就會(huì)將事件順著響應(yīng)者鏈條往上傳遞,傳遞給上一個(gè)響應(yīng)者
4.接著就會(huì)調(diào)用上一個(gè)響應(yīng)者的touches...方法

如何判斷上一個(gè)響應(yīng)者

1.如果當(dāng)前這個(gè)view是控制器的view,那么控制器就是上一個(gè)響應(yīng)者
2.如果當(dāng)前這個(gè)view不是控制器的view,那么父控件就是上一個(gè)響應(yīng)者

響應(yīng)者鏈的事件傳遞過程

1.如果view的控制器存在郑临,就傳遞給控制器;如果控制器不存在屑宠,則將其傳遞給它的父視圖

2.在視圖層次結(jié)構(gòu)的最頂級(jí)視圖牧抵,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對(duì)象進(jìn)行處理

3.如果window對(duì)象也不處理侨把,則其將事件或消息傳遞給UIApplication對(duì)象

4.如果UIApplication也不能處理該事件或消息犀变,則將其丟棄

一個(gè)小應(yīng)用來講解事件的攔截

點(diǎn)擊應(yīng)用.gif
// 監(jiān)聽按鈕的點(diǎn)擊
- (IBAction)PopClick:(PopButton *)sender {
    
    UIButton *chatView = [UIButton buttonWithType:UIButtonTypeCustom];
    
    [chatView setImage:[UIImage imageNamed:@"對(duì)話框"] forState:UIControlStateNormal];
    
    [chatView setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
    
    chatView.bounds = CGRectMake(0, -200, 200, 200);
    
    // 它的父控件為基準(zhǔn)
    chatView.center = CGPointMake(100, -100);
    
    // 將對(duì)話框加入父控件中 ,超出的部分仍然可以顯示秋柄, 只是不能點(diǎn)擊
    [sender addSubview:chatView];    
    sender.chatView = chatView;
}

///自定義按鈕,來實(shí)現(xiàn)變態(tài)攔截功能
// 外界提供_chatView用來接收新的對(duì)話框
// 重寫hitTest方法获枝,讓能點(diǎn)擊對(duì)話框。 只要攔截點(diǎn)擊就行了
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
     // 將點(diǎn)得坐標(biāo)系轉(zhuǎn)換
    CGPoint chatP =[self convertPoint:point toView:_chatView];
    
    // 判斷點(diǎn)是否在 控件上
    if ([_chatView pointInside:chatP withEvent:event]) {
        return _chatView;
    }
    // 若不在骇笔,執(zhí)行以前的方式
    return [super hitTest:point withEvent:event];

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint curOffset = [touch locationInView:self];
    CGPoint lastOffset = [touch previousLocationInView:self];
    
    CGFloat offsetX = curOffset.x -lastOffset.x;
    CGFloat offsetY = curOffset.y - lastOffset.y;
    
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末省店,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子笨触,更是在濱河造成了極大的恐慌懦傍,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,185評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芦劣,死亡現(xiàn)場離奇詭異粗俱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)虚吟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門寸认,熙熙樓的掌柜王于貴愁眉苦臉地迎上來签财,“玉大人,你說我怎么就攤上這事偏塞〕簦” “怎么了?”我有些...
    開封第一講書人閱讀 157,684評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵灸叼,是天一觀的道長神汹。 經(jīng)常有香客問我,道長古今,這世上最難降的妖魔是什么慎冤? 我笑而不...
    開封第一講書人閱讀 56,564評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮沧卢,結(jié)果婚禮上蚁堤,老公的妹妹穿的比我還像新娘。我一直安慰自己但狭,他們只是感情好披诗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,681評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著立磁,像睡著了一般呈队。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唱歧,一...
    開封第一講書人閱讀 49,874評(píng)論 1 290
  • 那天宪摧,我揣著相機(jī)與錄音,去河邊找鬼颅崩。 笑死几于,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沿后。 我是一名探鬼主播沿彭,決...
    沈念sama閱讀 39,025評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼尖滚!你這毒婦竟也來了喉刘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,761評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤漆弄,失蹤者是張志新(化名)和其女友劉穎睦裳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撼唾,經(jīng)...
    沈念sama閱讀 44,217評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廉邑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,545評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鬓催。...
    茶點(diǎn)故事閱讀 38,694評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肺素,死狀恐怖恨锚,靈堂內(nèi)的尸體忽然破棺而出宇驾,到底是詐尸還是另有隱情,我是刑警寧澤猴伶,帶...
    沈念sama閱讀 34,351評(píng)論 4 332
  • 正文 年R本政府宣布课舍,位于F島的核電站,受9級(jí)特大地震影響他挎,放射性物質(zhì)發(fā)生泄漏筝尾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,988評(píng)論 3 315
  • 文/蒙蒙 一办桨、第九天 我趴在偏房一處隱蔽的房頂上張望筹淫。 院中可真熱鬧,春花似錦呢撞、人聲如沸损姜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摧阅。三九已至,卻和暖如春绷蹲,著一層夾襖步出監(jiān)牢的瞬間棒卷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評(píng)論 1 266
  • 我被黑心中介騙來泰國打工祝钢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留比规,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,427評(píng)論 2 360
  • 正文 我出身青樓拦英,卻偏偏與公主長得像苞俘,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子龄章,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,580評(píng)論 2 349

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