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

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

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

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

第一響應(yīng)者

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

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

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

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

UIKit通過基于視圖的hit-testing來確認(rèn)觸摸事件的發(fā)生.UIKit會按照視圖的層級,逐層的按照觸摸視圖的位置和視圖的bouds進(jìn)行對比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

當(dāng)視圖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).補習(xí)了基本的數(shù)學(xué)知識之后,代碼修改如下:

@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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,436評論 2 378

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

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