OC_ 事件傳遞機制和響應者鏈

序言:翻閱資料僧叉,學習,探究棺榔,總結瓶堕,借鑒,謝謝探路者症歇,我只是個搬運工郎笆。
參考、轉發(fā)資料:
http://www.reibang.com/p/2e074db792ba
http://www.reibang.com/p/2b34ea0b6762
http://blog.csdn.net/a316212802/article/details/50061317

1. 事件的傳遞忘晤、響應流程宛蚓。

事件傳遞機制。首先要有個事件(這里我們我們只講解最常用的觸摸事件)设塔,然后怎么這個事件是被誰發(fā)現的存儲在哪凄吏,中間是怎么個傳遞過程,怎么判斷誰能執(zhí)行誰不能執(zhí)行闰蛔,最后誰處理痕钢。我想這些就是我們需要理解掌握的。

  • 事件源:
    我們研究的是觸摸事件钞护,觸摸事件其實也分為很多種盖喷。例如
    1. 輕擊手勢 TapGestureRecognizer
    2. 輕掃手勢 SwipeGestureRecognizer
    3. 長按手勢 LongPressGestureRecognizer
    4. 拖動手勢 PanGestureRecognizer
    5. 捏合手勢 PinchGestureRecognizer
    6. 旋轉手勢 RotationGestureRecognizer

為了方便以下講述的使用輕擊手勢。

  • 事件是被誰發(fā)現的存儲在哪
    當程序中發(fā)現觸摸事件之后难咕,系統(tǒng)會將事件加入到UIApplication管理的一個任務隊列中,以堆的形式存儲距辆,先進先出先執(zhí)行余佃。

    • UIApplication是什么?
      UIApplication對象是應用程序的象征跨算,每個應用都有一個自己的UIApplication對象爆土。在一個iOS程序啟動后創(chuàng)建的第一個對象就是UIApplication對象,所以在AppDelegate.m文件中執(zhí)行了UIApplicationDelegate方法供我們使用诸蚕,而且UIApplication對象以一個單例的形式創(chuàng)建的步势,所以在其他文件中都能通過單例的形式獲取到UIApplication對象。
  • 中間是怎么個傳遞過程背犯,怎么判斷誰能執(zhí)行誰不能執(zhí)行坏瘩,最后誰處理

  1. 系統(tǒng)會將事件加入到UIApplication管理的一個任務隊列中,以棧的形式存儲漠魏,先進先出先執(zhí)行倔矾。
  2. UIApplication會將事件發(fā)送給我們最底層的窗口UIWindow,這里的UIWindow值的是keyWindow(主),也只有顯示在keyWindow上的視圖才能接受點擊事件的響應哪自。
  3. UIWindow將事件發(fā)送給控制器(或者視圖)丰包,如果控制器能處理事件的響應,而且觸摸點在自己的身上壤巷,那么繼續(xù)尋找子視圖邑彪。
  4. 遍歷所有的子視圖,重復上一步的判斷胧华,以此循環(huán)寄症,直到條件不滿足。
  5. 到了這里表示上一步驟的條件不滿足撑柔。
- 如果找到的作用點在子視圖上瘸爽,但是子視圖的userInteractionEnabled屬性設置NO(是否響應的設置),那么這個事件就會被廢棄铅忿。
- 如果找不到觸摸點沒有在點擊的子視圖上剪决,那么這個事件由父視圖執(zhí)行。
- 如果摸點在子視圖的區(qū)域檀训,但是設置了隱藏或者透明度為0時柑潦,那這個事件同樣由父視圖執(zhí)行。

這就是判斷誰能執(zhí)行誰不能執(zhí)行峻凫,最后誰處理的理由渗鬼。

2. 具體如何通過代碼判斷

  1. hitTest:withEvent:方法
  - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{ 
}

這個方法可以返回最合適的view,什么叫最合適荧琼,首先這個方法的返回值譬胎,可以是自己,也以是子視圖命锄,也或者是nil堰乔。在我們不重寫這個方法的時候,就要子視圖是否滿足我們的查詢要求脐恩。

  1. pointInside:withEvent:方法
    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
  {
  }

方法判斷觸摸點在不在當前view上(方法調用者的坐標系上)如果返回YES镐侯,代表點在方法調用者的坐標系上;返回NO代表點不在方法調用者的坐標系上,那么方法調用者也就不能處理事件驶冒。

  1. 內部的實現大概是這樣的
  - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
  // 1.判斷下窗口能否接收事件
  if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil;
  // 2.判斷下點在不在窗口上
  // 不在窗口上
  if ([self pointInside:point withEvent:event] == NO) return nil;
  // 3.從后往前遍歷子控件數組
  int count = (int)self.subviews.count;
  for (int i = count - 1; i >= 0; i--)     {
      // 獲取子控件
      UIView *childView = self.subviews[i];
      // 坐標系的轉換,把窗口上的點轉換為子控件上的點
      // 把自己控件上的點轉換成子控件上的點
      CGPoint childP = [self convertPoint:point toView:childView];
      UIView *fitView = [childView hitTest:childP withEvent:event];
      if (fitView) {
          // 如果能找到最合適的view
          return fitView;
      }
  }
  // 4.沒有找到更合適的view苟翻,也就是沒有比自己更合適的view
  return self;
}
// 作用:判斷下傳入過來的點在不在方法調用者的坐標系上
// point:是方法調用者坐標系上的點
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
  // 判斷點在不在區(qū)域內
   if (CGRectContainsPoint(self.button.bounds, tempoint))
  {
      return YES ;
    }
return NO ;
}

3. 響應者鏈

  • UIResponder作為一個響應者事件,所有的可交互控件都是UIResponder直接或者間接的子類骗污。

  • 響應者鏈的起點為: 上面我們所尋找到的最最合適的View崇猫,而這個View將調用自身的
    touchesBegan:withEvent:方法來開始事件響應者鏈的起始傳遞。

  • 添加到屏幕中的視圖層級關系身堡,從最上層的可能是button的點擊事件邓尤,傳遞給父類或者自身的控制器并調用父類的touchesBegan:withEvent:方法。一直到最底層顯示的keyWindow上這就是響應者鏈的傳遞。響應者鏈是由多層視圖組成的結構(不管是View汞扎,還是控制器都是繼承于響應者類UIResponder的)季稳。由最上面的子控件傳遞給最底層的window。

  • 如何判斷上一個響應者

    • 如果當前這個view是控制器的view,那么控制器就是上一個響應者
    • 如果當前這個view不是控制器的view,那么父控件就是上一個響應者
  • 事件響應和響應者鏈的區(qū)別:

    • 事件響應是從最底層的keyWindow開始向上分發(fā)事件的澈魄,而響應者鏈是從最合適View開始向下傳遞的景鼠。 方向不同。

4. 實際案例痹扇。

  1. button部分視圖區(qū)域超出了父視圖铛漓,怎么實現點擊超出部分也執(zhí)行點擊效果。
    例子:
// ViewController.m
    - (void)viewDidLoad {
    [super viewDidLoad];
      AView *aView = [[AView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)] ;
    aView.backgroundColor = [UIColor grayColor] ;
    [self.view addSubview:aView] ;
    
    [aView _initViews] ;
  }
  // AView.m
  // 創(chuàng)建試圖
  - (void)_initViews
{
    self.button = [UIButton buttonWithType:UIButtonTypeCustom] ;
    self.button.frame = CGRectMake(-50, -50, 100, 100) ;
    self.button.backgroundColor = [UIColor redColor] ;
    [self.button addTarget:self action:@selector(buttoClick:) forControlEvents:UIControlEventTouchUpInside] ;
    [self addSubview:self.button] ;
}
// 點擊方法
  - (void)buttoClick:(id)sender
{
    NSLog(@"點擊了button") ;
}

如果在AView中不做任何處理鲫构,那么


分析圖.png
  • 解決辦法:
    在AView中重寫hitTest:withEvent:方法
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    if (view == nil) {
/*
       // 將像素point由point所在視圖轉換到目標視圖view中浓恶,返回在目標視圖view中的像素值
       - (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;
     // 將像素point從view中轉換到當前視圖中,返回在當前視圖中的像素值
       - (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;
     */
          CGPoint tempoint = [self.button convertPoint:point fromView:self];
        if (CGRectContainsPoint(self.button.bounds, tempoint))
        {
            view = self.button;
          }
      }
      return view;
}  
  ```
當點擊的觸摸手勢在Button的視圖上结笨,這樣手動判斷返回我們指定的View為Button包晰,就可以了。

2. 有時候我們經常有這樣的需求炕吸,在子視圖View中想拿到View所在的控制器進行一些操作伐憾,通過事件響應者鏈尋找子視圖所在的控制器。(這里我們拋棄基礎的通過superView的方法獲群漳!)
首先我們要明白一點就是:可交互控件都是UIResponder直接或者間接的子類树肃。

import "UIView+ViewController.h"

@implementation UIView (ViewController)
/*
為UIView擴展一個類目,通過這個方法可以獲取這個視圖所在的控制器
*/

  • (UIViewController *)viewController
    {
    // 獲取當前對象的下一響應者
    UIResponder *nextResp = self.nextResponder;
    while (![nextResp isKindOfClass:[UIViewController class]] && nextResp != nil) {
    // 獲取nextResp對象的下一響應者
    nextResp = nextResp.nextResponder;
    }
    return (UIViewController *)nextResp;
    }
使用方法瀑罗,例如我們還是在我們剛寫的deme中的button點擊方法中使用胸嘴,看看效果,記得導入類別斩祭。
  • (void)buttoClick:(id)sender
    {
    NSLog(@"點擊了button") ;
    UIViewController *vc = [self viewController] ;
    NSLog(@"%@",[NSString stringWithUTF8String:object_getClassName(vc)]) ;
    // 2017-03-17 10:57:57.289 Init[2384:473093] 點擊了button
    // 2017-03-17 10:57:57.290 Init[2384:473093] ViewController
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末筛谚,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子停忿,更是在濱河造成了極大的恐慌,老刑警劉巖蚊伞,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件席赂,死亡現場離奇詭異,居然都是意外死亡时迫,警方通過查閱死者的電腦和手機颅停,發(fā)現死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掠拳,“玉大人癞揉,你說我怎么就攤上這事。” “怎么了喊熟?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵柏肪,是天一觀的道長。 經常有香客問我芥牌,道長烦味,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任壁拉,我火速辦了婚禮谬俄,結果婚禮上,老公的妹妹穿的比我還像新娘弃理。我一直安慰自己溃论,他們只是感情好,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布痘昌。 她就那樣靜靜地躺著钥勋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪控汉。 梳的紋絲不亂的頭發(fā)上笔诵,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音姑子,去河邊找鬼乎婿。 笑死,一個胖子當著我的面吹牛街佑,可吹牛的內容都是我干的谢翎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼沐旨,長吁一口氣:“原來是場噩夢啊……” “哼森逮!你這毒婦竟也來了?” 一聲冷哼從身側響起磁携,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤褒侧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谊迄,有當地人在樹林里發(fā)現了一具尸體闷供,經...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年统诺,在試婚紗的時候發(fā)現自己被綠了歪脏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡粮呢,死狀恐怖婿失,靈堂內的尸體忽然破棺而出钞艇,到底是詐尸還是另有隱情,我是刑警寧澤豪硅,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布哩照,位于F島的核電站,受9級特大地震影響舟误,放射性物質發(fā)生泄漏葡秒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一嵌溢、第九天 我趴在偏房一處隱蔽的房頂上張望眯牧。 院中可真熱鬧,春花似錦赖草、人聲如沸学少。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽版确。三九已至,卻和暖如春乎折,著一層夾襖步出監(jiān)牢的瞬間绒疗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工骂澄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吓蘑,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓坟冲,卻偏偏與公主長得像磨镶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子健提,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內容