hitTest和pointInside方法你真的熟嗎?

杭城小劉發表於2019-03-04

hittest方法

  • 就是用來尋找最合適的view
  • 當一個事件傳遞給一個控制元件,就會呼叫這個控制元件的hitTest方法
  • 點選了白色的view: 觸控事件 -> UIApplication -> UIWindow 呼叫 [UIWindow hitTest] -> 白色view [WhteView hitTest]

實驗1:

定義 BaseView,在裡面實現方法touchBegan,監聽當前哪個類呼叫了該方法。

定義KeyWindow,在裡面實現hitTest方法,監聽哪個類呼叫了該方法,用來追蹤判斷哪個view是最合適的view

在控制器的介面上加5個顏色不同的view,每個view自定義view去實現,因此在不同的view上的手勢就可以由不同的view攔截到。

UI效果圖
//KeyWindow

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
NSLog(@"fittest->%@",view);
return view;
}
複製程式碼

結果:

點選了白色1:

2017-10-11 16:48:52.882547+0800 主流App框架[16295:358790] BrownView--hitTest withEvent
2017-10-11 16:48:59.646610+0800 主流App框架[16295:358790] GreenView--hitTest withEvent
2017-10-11 16:48:59.647145+0800 主流App框架[16295:358790] fittest-><UIView: 0x7f8f23406510; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60c000221840>>
2017-10-11 16:48:59.647575+0800 主流App框架[16295:358790] BrownView--hitTest withEvent
2017-10-11 16:48:59.647702+0800 主流App框架[16295:358790] GreenView--hitTest withEvent
2017-10-11 16:48:59.647880+0800 主流App框架[16295:358790] fittest-><UIView: 0x7f8f23406510; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60c000221840>>
複製程式碼

點選了藍色3:

2017-10-11 16:49:56.331024+0800 主流App框架[16295:358790] BrownView--hitTest withEvent
2017-10-11 16:49:56.331335+0800 主流App框架[16295:358790] BView--hitTest withEvent
2017-10-11 16:49:56.331617+0800 主流App框架[16295:358790] BlueView--hitTest withEvent
2017-10-11 16:49:56.331968+0800 主流App框架[16295:358790] YellowView--hitTest withEvent
2017-10-11 16:49:56.333206+0800 主流App框架[16295:358790] fittest-><BlueView: 0x7f8f23406f10; frame = (19 21; 240 128); autoresize = RM+BM; layer = <CALayer: 0x60c0002218c0>>
2017-10-11 16:49:56.333633+0800 主流App框架[16295:358790] BrownView--hitTest withEvent
2017-10-11 16:49:56.333762+0800 主流App框架[16295:358790] BView--hitTest withEvent
2017-10-11 16:49:56.333893+0800 主流App框架[16295:358790] BlueView--hitTest withEvent
2017-10-11 16:49:56.334005+0800 主流App框架[16295:358790] YellowView--hitTest withEvent
2017-10-11 16:49:56.334185+0800 主流App框架[16295:358790] fittest-><BlueView: 0x7f8f23406f10; frame = (19 21; 240 128); autoresize = RM+BM; layer = <CALayer: 0x60c0002218c0>>
2017-10-11 16:49:56.334644+0800 主流App框架[16295:358790] BlueView
複製程式碼

那麼看出來hitTest方法的作用就是找出最合適的view,那麼我們可以指定任何事情的最合適的view為特定的view

實驗2:

在KeyWindow中hitTest方法中返回BlueView,那麼點選任何色塊的view那麼都會交給BlueView去處理事件。

//KeyWindow
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
return self.subviews.firstObject.subviews.firstObject;
}
複製程式碼

結果:

2017-10-11 22:48:46.102793+0800 主流App框架[21498:749663] GreenView
2017-10-11 22:48:46.668595+0800 主流App框架[21498:749663] GreenView
複製程式碼

因為事件的響應者鏈條就是當使用者操作螢幕會產生一個事件,該事件被系統加入到事件佇列中去,UIApplication物件會將事件佇列中最早加入進去的事件傳遞給window,然後window找到最合適的view去處理事件。因此任何事件都會先通過KeyWindow物件去判斷並找到最合適的view

2個重要的方法

  • -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event: 用來判斷觸控點是否在控制元件上

  • -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event: 用來判斷控制元件是否接受事件以及找到最合適的view

模仿系統實現找出最合適的view

//KeyWindow

/**
模仿系統實現尋找最合適的view步驟
1、控制元件接收事件
2、觸控點在自己身上
3、從後往前遍歷子控制元件,重複前面2個步驟
4、如果沒有符合條件的子控制元件,那麼就自己最合適

*/

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}

if (![self pointInside:point withEvent:event]) {
return nil;
}

for (NSUInteger index = self.subviews.count - 1; index >= 0; index--) {
CGPoint childViewPoint = [self convertPoint:point toView:self.subviews[index]];
UIView *fitestView = [self.subviews[index] hitTest:childViewPoint withEvent:event];
if (fitestView) {
return fitestView;
}
return nil;
}

return self;
}
複製程式碼

給出 一個Demo地址:github.com/FantasticLB…

實驗:

在控制器(ViewController)的view上先新增一個UIButton,再新增一個自定義的UIView(ShelterView),蓋在button的上面。

需求:點選ShelterView上的點,如果點也在UIButton範圍上則交給UIButton處理事件,如果不在UIButton上則交給ShelterView處理,如果點選螢幕上除了ShelterView之外的點則交給控制器的view處理。

//ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"viewController->%s",__func__);
}


//ShelterView
#import "ShelterView.h"

@implementation ShelterView

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
/**
需求:不管點選按鈕還是view都交給button處理
思路:在view的hitTest方法中尋找最合適的view,那麼返回nil告訴系統view不是最合適的view,那麼系統則認為按鈕是最合適的view
return nil;
*/

//需求,在view上點選,如果點選範圍在button上則由button進行處理事件;否則交給view處理事件

UIView *button = nil;
for (UIView *subView in self.superview.subviews) {
//判斷事件的點是否在按鈕上
if ([subView isKindOfClass:[UIButton class]]) {
button =subView;
}


CGPoint btnPoint = [self convertPoint:point toView:button];
if ([button pointInside:btnPoint withEvent:event]) {
return button;
}else{
//此時代表事件觸控點不在button上,但是也不能寫nil,寫nil的話點選螢幕上的其他地方系統會尋找最合適的view,此時返回nil( return nil;),則代表view不是最合適的view,那麼此時點選螢幕上除了按鈕之外的區域,最合適的view就是控制器上面的view
return [super hitTest:point withEvent:event];
}
}
return nil;
}

@end
複製程式碼

要看完整Demo,地址為:github.com/FantasticLB…

相關文章