UIView中的hitTest方法

1. 事件響應(yīng)的過程

在iOS中的view之間逐層疊加蛀醉,當點擊了屏幕上的某個view時案训,這個點擊動作會由硬件層傳導(dǎo)到操作系統(tǒng)并生成一個事件(Event)勒魔,這個事件順著view的層級由下往上傳導(dǎo)建钥,直至找到包含有這個點擊點壶笼、層級最高、且可與用戶交互的view來響應(yīng)這個事件腿宰。

iOS中的事件響應(yīng)鏈

2. 響應(yīng)鏈中涉及的方法

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

// 點位轉(zhuǎn)換相關(guān)方法
- (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
- (CGPoint)convertPoint:(CGPoint)point fromView:(nullable UIView *)view;
- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
- (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view;

hitTest方法的實現(xiàn):

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    //系統(tǒng)默認會忽略isUserInteractionEnabled設(shè)置為NO、隱藏吃度、alpha小于等于0.01的視圖
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}
  1. 點擊事件會在hitTest甩挫、pointInside兩個方法的配合下,向下傳遞椿每;
  2. 在hitTest:withEvent:內(nèi)部首先會判斷該視圖是否能響應(yīng)觸摸事件伊者,如果不能響應(yīng)英遭,返回nil,表示該視圖不響應(yīng)此觸摸事件亦渗。然后再調(diào)用pointInside:withEvent:(該方法用來判斷點擊事件發(fā)生的位置是否處于當前視圖處理范圍)挖诸。如果pointInside:withEvent:返回NO,那么hiteTest:withEvent:也直接返回nil央碟;
  3. 如果pointInside:withEvent:返回YES税灌,則向當前視圖的所有子視圖發(fā)送hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖亿虽,即從subviews數(shù)組的末尾向前遍歷菱涤。直到有子視圖返回非空對象或者全部子視圖遍歷完畢;若第一次有子視圖返回非空對象洛勉,則 hitTest:withEvent:方法返回此對象粘秆,處理結(jié)束;如所有子視圖都返回非收毫,則hitTest:withEvent:方法返回該視圖自身攻走。
  • UIResponder中的touchesBegan、touchesMoved此再、touchesEnded等方法
// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application.  Failure to
// do so is very likely to lead to incorrect behavior or crashes.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

// 這幾個方法比較常用昔搂,在此不再敖述;
// 當然输拇,UIResponder中不止這三個響應(yīng)事件的方法摘符,本文僅以touches的這三個方法為例。
  • 示例

為了使我們更好的理解事件響應(yīng)過程中策吠,上述UIView與UIResponder這幾個方法的執(zhí)行過程逛裤,我們用以下圖示例(示例參考文章)進行說明,圖中視圖ABCDE(UIView型)之間的層次關(guān)系是self.view(A(B, C(D, E))):

測試hitTest方法的執(zhí)行過程

以下代碼是在A視圖中都重寫我們需要觀察的幾個父類方法猴抹,BCDE中需要重寫的代碼以此類推:

/*
* 例如:A中重寫父類方法的代碼带族,
*/

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
    NSLog(@"AView ---->> hitTest:withEvent: ---");
    UIView * view = [super hitTest:point withEvent:event];
    NSLog(@"AView <<--- hitTest:withEvent: --- /n hitTestView:%@", view);
    return view;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
    
    NSLog(@"AView --->> pointInside:withEvent: ---");
    BOOL isInside = [super pointInside:point withEvent:event];
    NSLog(@"AView <<--- pointInside:withEvent: --- isInside:%d", isInside);
    return isInside;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    NSLog(@"AView touchesBegan");
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    
    NSLog(@"AView touchesMoved");
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    
    NSLog(@"AView touchesEnded");
}

當點擊了一下B視圖所在區(qū)域時,Xcode輸出log如下:

點擊B視圖蟀给,Xcode輸出的log

在示例中可以發(fā)現(xiàn)響應(yīng)鏈中所涉及方法的執(zhí)行過程蝙砌,有以下特點

  1. 當UIView中的isUserInteractionEnabled = NO、isHidden = YES坤溃、alpha <= 0.01時拍霜,hitTest方法不會被調(diào)用;
  2. UIResponder 中的touches三個方法都是發(fā)生在找到最終的響應(yīng)事件的view之后薪介;
  3. 二是尋找hit-test view的事件鏈傳導(dǎo)了兩遍,具體原因不明;
  4. 請自行修改兩個方法的返回值越驻,來實驗汁政。

3. hitTest方法的應(yīng)用

3.1 改變UIButton的響應(yīng)熱區(qū)

具體的說改變視圖的響應(yīng)熱區(qū)道偷,主要是在pointInside方法中完成的,QiShare關(guān)于改變熱區(qū)的文章中有過描述记劈。但是hitTest勺鸦、pointInside同屬響應(yīng)鏈中方法,如果有需求目木,也可以在hitTest中返回一個確定的view换途。

3.2 view超出superView的bounds仍能響應(yīng)事件

如圖,在黃色superView上添加一個UIButton刽射,UIButton上半部分超出superView军拟。正常的情況下點擊紅框區(qū)域時,UIButton是無法響應(yīng)點擊事件的誓禁,要讓紅框區(qū)域內(nèi)的UIButton仍能響應(yīng)點擊事件懈息,需要我們重寫superView的hitTest方法。


#import "BeyondBoundsOfView.h"

@interface BeyondBoundsOfView ()

@property (nonatomic, strong) UIButton *button;

@end

@implementation BeyondBoundsOfView

- (instancetype)initWithFrame:(CGRect)frame {
    
    self = [super initWithFrame:frame];
    if (self) {
        _button = [UIButton buttonWithType:UIButtonTypeSystem];
        [_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [_button setTitle:@"UIButton" forState:UIControlStateNormal];
        [_button setBackgroundColor:[UIColor lightGrayColor]];
        _button.frame = CGRectMake(0, 0, 80, 80);
        [self addSubview:_button];
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    CGSize size = self.frame.size;
    _button.center = CGPointMake(size.width / 2, 0);
}


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    
    for (UIView *subview in self.subviews) {
        CGPoint convertedPoint = [subview convertPoint:point fromView:self];
        UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
        if (hitTestView) {
            return hitTestView;
        }
    }
    return nil;
}

@end

上面代碼中關(guān)鍵的一行:
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
獲取到convertedPoint對我們循環(huán)調(diào)用子view的hitTest很關(guān)鍵摹恰。

工程源碼GitHub地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辫继,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俗慈,更是在濱河造成了極大的恐慌姑宽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闺阱,死亡現(xiàn)場離奇詭異炮车,居然都是意外死亡,警方通過查閱死者的電腦和手機馏颂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門示血,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人救拉,你說我怎么就攤上這事难审。” “怎么了亿絮?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵告喊,是天一觀的道長。 經(jīng)常有香客問我派昧,道長黔姜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任蒂萎,我火速辦了婚禮秆吵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘五慈。我一直安慰自己纳寂,他們只是感情好主穗,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著毙芜,像睡著了一般忽媒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腋粥,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天晦雨,我揣著相機與錄音,去河邊找鬼隘冲。 笑死闹瞧,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的对嚼。 我是一名探鬼主播夹抗,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纵竖!你這毒婦竟也來了漠烧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤靡砌,失蹤者是張志新(化名)和其女友劉穎已脓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體通殃,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡度液,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了画舌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堕担。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖曲聂,靈堂內(nèi)的尸體忽然破棺而出霹购,到底是詐尸還是另有隱情,我是刑警寧澤朋腋,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布齐疙,位于F島的核電站,受9級特大地震影響旭咽,放射性物質(zhì)發(fā)生泄漏贞奋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一穷绵、第九天 我趴在偏房一處隱蔽的房頂上張望轿塔。 院中可真熱鬧,春花似錦、人聲如沸催训。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漫拭。三九已至,卻和暖如春混稽,著一層夾襖步出監(jiān)牢的瞬間采驻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工匈勋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留礼旅,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓洽洁,卻偏偏與公主長得像痘系,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子饿自,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345