iOS-事件響應(yīng)鏈

App通過響應(yīng)者對象來接收和處理事件坪仇,響應(yīng)者對象都是UIResponder的子類對象拉背,常見的UIView芍碧,UIVieController和UIApplication都是UIResponder的子類.響應(yīng)者接收事件原始數(shù)據(jù)之后必須進行事件處理或者轉(zhuǎn)發(fā)給其他的響應(yīng)者對象.

當前App應(yīng)用程接收到一個事件飞醉,UIKit會自動找到最合適的響應(yīng)者對象,也就是常說的第一響應(yīng)者.UIKit定義了響應(yīng)者對象是如何進行事件轉(zhuǎn)發(fā)的奠涌,常見的響應(yīng)者鏈:

事件響應(yīng)者鏈.png

第一響應(yīng)者

對于不同類型的事件,UIKit根據(jù)事件類型將事件對象傳遞給對應(yīng)的響應(yīng)者.

觸摸事件:第一響應(yīng)者就是觸摸發(fā)生的視圖.
焦點事件:第一響應(yīng)者就是具有焦點的容器控件.
搖晃事件:需要自己或UIKit指定第一響應(yīng)者.
遠程事件:需要自己或UIKit指定第一響應(yīng)者.
編輯菜單消息事件:需要自己或UIKit指定第一響應(yīng)者.

加速度計磷杏,陀螺儀及磁強計有關(guān)的運動事件不遵循響應(yīng)鏈.

觸摸事件響應(yīng)者

UIKit通過基于視圖的hit-testing來確認觸摸事件的發(fā)生.UIKit會按照視圖的層級溜畅,逐層的按照觸摸視圖的位置和視圖的bouds進行對比hitTest(_:with:)
來尋找包含觸摸事件最深層次的視圖,這個視圖就會成為觸摸事件的第一響應(yīng)者.

如果父視圖不能響應(yīng)觸摸事件极祸,那么所有的子視圖也不會響應(yīng)慈格,即使子視圖的位置超出父視圖的bounds也不會響應(yīng).

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   // default returns YES if point is in bounds

當視圖hidden = YES,禁用用戶操作(userInteractionEnabled=YES遥金,透明度小于0.01的時候講hitTest將不會執(zhí)行.

響應(yīng)者鏈

響應(yīng)者鏈通常是UIView組成的視圖層級浴捆,假設(shè)第一響應(yīng)者對象是UIView是視圖控制器的根view,next responder是它的視圖控制器稿械,或者是它的父視圖.

如果視圖控制器是window的根控制器选泻,next responder是window對象,如果控制器是被presented出來的美莫,那么next responder是它的父控制器.最終找到UIWindow對象.

UIWiddow的next responder是UIApplication對象.

UIApplication的next responder是app delegate.

尋找順序如下:
First Responser --> The Window --> The Application --> nil(丟棄)

實際應(yīng)用

Hit-Tesing 過程是從上向下(從父視圖到子視圖)遍歷页眯;Response Chain是 事件處理傳遞是從下向上(從子視圖到父視圖)傳遞。

關(guān)于事件響應(yīng)者鏈有一個場景項目會經(jīng)常遇到厢呵,按鈕大小窝撵,經(jīng)常會被產(chǎn)品經(jīng)理要求按鈕點擊不靈敏,要求擴大點擊區(qū)域.

1.自定義按鈕襟铭,重寫pointInside事件:

@implementation FEButton
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect bounds = self.bounds;
    //若原熱區(qū)小于44x44碌奉,則放大熱區(qū)短曾,否則保持原大小不變
    CGFloat widthDelta = MAX(44.0 - bounds.size.width, 0);
    CGFloat heightDelta = MAX(44.0 - bounds.size.height, 0);
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
    NSLog(@"FlyElephant---被點擊了:%@----點擊的點:%@",NSStringFromCGRect(bounds), NSStringFromCGPoint(point));
    return CGRectContainsPoint(bounds, point);
}

@end

2.項目中經(jīng)常會用到圓角,這個時候有人告訴你道批,兄弟错英,我只想點擊圓角內(nèi)的區(qū)域響應(yīng)事件,旁邊的空白不希望響應(yīng).補習了基本的數(shù)學知識之后隆豹,代碼修改如下:

@implementation CircleButton

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    CGFloat halfWidth = self.bounds.size.width / 2;
    
    CGFloat xDistance = point.x - halfWidth;
    
    CGFloat yDistance = point.y - halfWidth;
    
    CGFloat radius = sqrt(xDistance * xDistance + yDistance * yDistance);
    
    NSLog(@"HaldWidth:%f---point:%@---x軸距離:%f---y軸距離:%f--半徑:%f",halfWidth,NSStringFromCGPoint(point),xDistance,yDistance,radius);
    
    return radius <= halfWidth;
}

@end

3.自定義太多按鈕椭岩,心好累,最終還是擴展UIButton璃赡,提供工作效率.

@interface UIButton (FlyElephant)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end
#import "UIButton+FlyElephant.h"
#import <objc/runtime.h>

@implementation UIButton (FlyElephant)

static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
    if(value) {
        UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
    }else {
        return UIEdgeInsetsZero;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }
    
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    
    return CGRectContainsPoint(hitFrame, point);
}

@end

UI測試代碼:

- (void)setUp {
    
    UIView *bgView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    bgView.backgroundColor = [UIColor redColor];
    [self.view addSubview:bgView];
    
    FEButton *button = [[FEButton alloc] initWithFrame:CGRectMake(20, 20, 20, 20)];
    button.backgroundColor = [UIColor greenColor];
    [bgView addSubview:button];
    
    UIView *bgView1 = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 100, 100)];
    bgView1.backgroundColor = [UIColor redColor];
    [self.view addSubview:bgView1];
    

    CircleButton *button1 = [[CircleButton alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    button1.backgroundColor = [UIColor greenColor];
    button1.clipsToBounds = YES;
    button1.layer.cornerRadius = 50;
    [button1 addTarget:self action:@selector(buttonAction1:) forControlEvents:UIControlEventTouchUpInside];
    [bgView1 addSubview:button1];
    
    
    UIView *bgView2 = [[UIView alloc] initWithFrame:CGRectMake(100, 500, 100, 100)];
    bgView2.backgroundColor = [UIColor redColor];
    [self.view addSubview:bgView2];
    
    UIButton *button2 = [[UIButton alloc] initWithFrame:CGRectMake(20, 20, 20, 20)];
    button2.backgroundColor = [UIColor greenColor];
    button2.hitTestEdgeInsets = UIEdgeInsetsMake(-20, -20, -20, -20);
    [button2 addTarget:self action:@selector(buttonAction2:) forControlEvents:UIControlEventTouchUpInside];
    
    [bgView2 addSubview:button2];
}

- (void)buttonAction1:(UIButton *)sender {
    NSLog(@"FlyElephant---圓形擴大點擊區(qū)域");
}

- (void)buttonAction2:(UIButton *)sender {
    NSLog(@"Runtime擴大點擊");
}
FlyElephant.png

參考鏈接
https://developer.apple.com/documentation/uikit/understanding_event_handling_responders_and_the_responder_chain
https://stackoverflow.com/questions/808503/uibutton-making-the-hit-area-larger-than-the-default-hit-area
http://www.cnblogs.com/wengzilin/p/4249847.html?utm_source=tuicool&utm_medium=referral

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末判哥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子碉考,更是在濱河造成了極大的恐慌塌计,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侯谁,死亡現(xiàn)場離奇詭異锌仅,居然都是意外死亡,警方通過查閱死者的電腦和手機墙贱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門热芹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惨撇,你說我怎么就攤上這事伊脓。” “怎么了魁衙?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵报腔,是天一觀的道長。 經(jīng)常有香客問我剖淀,道長纯蛾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任纵隔,我火速辦了婚禮茅撞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巨朦。我一直安慰自己米丘,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布糊啡。 她就那樣靜靜地躺著拄查,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棚蓄。 梳的紋絲不亂的頭發(fā)上堕扶,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天碍脏,我揣著相機與錄音,去河邊找鬼稍算。 笑死典尾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的糊探。 我是一名探鬼主播钾埂,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼科平!你這毒婦竟也來了褥紫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瞪慧,失蹤者是張志新(化名)和其女友劉穎髓考,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弃酌,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡氨菇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妓湘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片查蓉。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖多柑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情楣责,我是刑警寧澤竣灌,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站秆麸,受9級特大地震影響初嘹,放射性物質(zhì)發(fā)生泄漏呐芥。R本人自食惡果不足惜茫死,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望首启。 院中可真熱鬧房铭,春花似錦驻龟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凌蔬,卻和暖如春露懒,著一層夾襖步出監(jiān)牢的瞬間闯冷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工懈词, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛇耀,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓坎弯,卻偏偏與公主長得像纺涤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荞怒,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 前言 當用戶點擊付款跳轉(zhuǎn)到付款界面洒琢、點擊掃一掃進入掃描二維碼視圖。當我們點擊屏幕的時候褐桌,這個點擊事件由硬件層傳向i...
    風與鸞閱讀 1,250評論 0 0
  • 一. Hit-Testing 什么是Hit-Testing?對于觸摸事件, window首先會嘗試將事件交給事件觸...
    面糊閱讀 815評論 0 50
  • 響應(yīng)者衰抑、UITouch 和 UIEvent 在iOS中,能夠響應(yīng)事件的對象都是UIResponder的子類對象荧嵌。U...
    就叫yang閱讀 1,233評論 0 4
  • 這個問題啊經(jīng)常問呛踊,網(wǎng)上資料非常多,但是自己老是答不好: 響應(yīng)鏈:響應(yīng)事件的一系列響應(yīng)者組成的一個層次結(jié)構(gòu)啦撮。 事件谭网,...
    iCoreMan閱讀 206評論 0 0
  • 昨天看了會俄城雷霆VS丹佛掘金隊的NBA季前賽。為什么我會對這無關(guān)痛癢的季前賽如此上心呢赃春?皆因球隊管理層在今夏大刀...
    回首燈火不見闌珊_4181閱讀 185評論 0 0