序言:翻閱資料僧叉,學習,探究棺榔,總結瓶堕,借鑒,謝謝探路者症歇,我只是個搬運工郎笆。
參考、轉發(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í)行闰蛔,最后誰處理痕钢。我想這些就是我們需要理解掌握的。
- 事件源:
我們研究的是觸摸事件钞护,觸摸事件其實也分為很多種盖喷。例如- 輕擊手勢 TapGestureRecognizer
- 輕掃手勢 SwipeGestureRecognizer
- 長按手勢 LongPressGestureRecognizer
- 拖動手勢 PanGestureRecognizer
- 捏合手勢 PinchGestureRecognizer
- 旋轉手勢 RotationGestureRecognizer
為了方便以下講述的使用輕擊手勢。
-
事件是被誰發(fā)現的存儲在哪
當程序中發(fā)現觸摸事件之后难咕,系統(tǒng)會將事件加入到UIApplication管理的一個任務隊列中,以堆的形式存儲距辆,先進先出先執(zhí)行余佃。- UIApplication是什么?
UIApplication對象是應用程序的象征跨算,每個應用都有一個自己的UIApplication對象爆土。在一個iOS程序啟動后創(chuàng)建的第一個對象就是UIApplication對象,所以在AppDelegate.m文件中執(zhí)行了UIApplicationDelegate方法供我們使用诸蚕,而且UIApplication對象以一個單例的形式創(chuàng)建的步势,所以在其他文件中都能通過單例的形式獲取到UIApplication對象。
- UIApplication是什么?
中間是怎么個傳遞過程背犯,怎么判斷誰能執(zhí)行誰不能執(zhí)行坏瘩,最后誰處理
- 系統(tǒng)會將事件加入到UIApplication管理的一個任務隊列中,以棧的形式存儲漠魏,先進先出先執(zhí)行倔矾。
- UIApplication會將事件發(fā)送給我們最底層的窗口UIWindow,這里的UIWindow值的是keyWindow(主),也只有顯示在keyWindow上的視圖才能接受點擊事件的響應哪自。
- UIWindow將事件發(fā)送給控制器(或者視圖)丰包,如果控制器能處理事件的響應,而且觸摸點在自己的身上壤巷,那么繼續(xù)尋找子視圖邑彪。
- 遍歷所有的子視圖,重復上一步的判斷胧华,以此循環(huán)寄症,直到條件不滿足。
- 到了這里表示上一步驟的條件不滿足撑柔。
- 如果找到的作用點在子視圖上瘸爽,但是子視圖的userInteractionEnabled屬性設置NO(是否響應的設置),那么這個事件就會被廢棄铅忿。
- 如果找不到觸摸點沒有在點擊的子視圖上剪决,那么這個事件由父視圖執(zhí)行。
- 如果摸點在子視圖的區(qū)域檀训,但是設置了隱藏或者透明度為0時柑潦,那這個事件同樣由父視圖執(zhí)行。
這就是判斷誰能執(zhí)行誰不能執(zhí)行峻凫,最后誰處理的理由渗鬼。
2. 具體如何通過代碼判斷
-
hitTest:withEvent:
方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
}
這個方法可以返回最合適的view,什么叫最合適荧琼,首先這個方法的返回值譬胎,可以是自己,也以是子視圖命锄,也或者是nil堰乔。在我們不重寫這個方法的時候,就要子視圖是否滿足我們的查詢要求脐恩。
-
pointInside:withEvent:
方法
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
}
方法判斷觸摸點在不在當前view上(方法調用者的坐標系上)如果返回YES镐侯,代表點在方法調用者的坐標系上;返回NO代表點不在方法調用者的坐標系上,那么方法調用者也就不能處理事件驶冒。
- 內部的實現大概是這樣的
- (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. 實際案例痹扇。
- 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中不做任何處理鲫构,那么
- 解決辦法:
在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
}