處理事件的方法
- UIView是UIResponder的子類,可以覆蓋下列4個方法處理不同的觸摸事件
//一根或者多根手指開始觸摸view
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//一根或者多根手指在view上移動
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
//一根或者多根手指離開view
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
//觸摸結(jié)束前耙册,某個系統(tǒng)事件(例如電話呼入)會打斷觸摸過程
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
UITouch對象
當(dāng)用戶用一根手指觸摸屏幕時,會創(chuàng)建一個與手指相關(guān)聯(lián)的UITouch對象 一根手指對應(yīng)一個UITouch對象
-
UITouch的作用:
- 保存著跟手指相關(guān)的信息迅涮,比如觸摸的位置囊颅、時間、階段
- 當(dāng)手指移動時蚓曼,系統(tǒng)會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置
- 當(dāng)手指離開屏幕時钦扭,系統(tǒng)會銷毀相應(yīng)的UITouch對象
-
UITouch的常見屬性
//觸摸產(chǎn)生時所處的窗口 @property(nonatomic,readonly,retain) UIWindow *window; //觸摸產(chǎn)生時所處的視圖 @property(nonatomic,readonly,retain) UIView *view; //短時間內(nèi)點按屏幕的次數(shù)纫版,可以根據(jù)tapCount判斷單擊、雙擊或更多的點擊 @property(nonatomic,readonly) NSUInteger tapCount; //記錄了觸摸事件產(chǎn)生或變化時的時間客情,單位是秒 @property(nonatomic,readonly) NSTimeInterval timestamp; //當(dāng)前觸摸事件所處的狀態(tài) @property(nonatomic,readonly) UITouchPhase phase;
UITouch的常見方法
//返回值表示觸摸在view上的位置
//這里返回的位置是針對view的坐標(biāo)系的(以view的左上角為原點(0, 0))
//調(diào)用時傳入的view參數(shù)為nil的話其弊,返回的是觸摸點在UIWindow的位置
- (CGPoint)locationInView:(UIView *)view;
// 該方法記錄了前一個觸摸點的位置
- (CGPoint)previousLocationInView:(UIView *)view;
UIEvent對象
每產(chǎn)生一個事件癞己,就會產(chǎn)生一個UIEvent對象
- UIEvent : 稱為事件對象,記錄事件產(chǎn)生的時刻和類型
- 常見屬性 :
//事件類型
//@property(nonatomic,readonly) UIEventType type;
//@property(nonatomic,readonly) UIEventSubtype subtype;
//事件產(chǎn)生的時間
@property(nonatomic,readonly) NSTimeInterval timestamp;
UIEvent還提供了相應(yīng)的方法可以獲得在某個view上面的觸摸對象(UITouch)
事件的產(chǎn)生和傳遞
發(fā)生觸摸事件后梭伐,系統(tǒng)會將該事件加入到一個由UIApplication管理的事件隊列中
UIApplication會從事件隊列中取出最前面的事件痹雅,并將事件分發(fā)下去以便處理,通常糊识,先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)
主窗口會在視圖層次結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸事件绩社,這也是整個事件處理過程的第一步
找到合適的視圖控件后,就會調(diào)用視圖控件的touches方法來作具體的事件處理
touchesBegan…
touchesMoved…
touchedEnded…這些touches方法的默認(rèn)做法是將事件順著響應(yīng)者鏈條向上傳遞(不實現(xiàn)touches方法赂苗,系統(tǒng)會自動向上一個響應(yīng)者傳遞)愉耙,將事件交給上一個響應(yīng)者進(jìn)行處理
如果一個事件既想自己處理也想交給上一個響應(yīng)者處理,那么自己實現(xiàn)touches方法哑梳,并且調(diào)用super的touches方法,[super touches、绘盟、鸠真、];
如何找到最合適的控件來處理事件
- 自己是否能接收觸摸事件龄毡?
- 觸摸點是否在自己身上吠卷?
- 從后往前遍歷子控件,重復(fù)前面的兩個步驟
- 如果沒有符合條件的子控件沦零,那么就自己最適合處理
注意點
如果父控件不能接收觸摸事件祭隔,那么子控件就不可能接收到觸摸事件(掌握)
UIView不接收觸摸事件的三種情況:
不接收用戶交互 : userInteractionEnabled = NO
隱藏 : hidden = YES
透明 : alpha = 0.0 ~ 0.01
UIImageView的userInteractionEnabled默認(rèn)就是NO,因此UIImageView以及它的子控件默認(rèn)是不能接收觸摸事件的
響應(yīng)者鏈
- 響應(yīng)者鏈條:是由多個響應(yīng)者對象連接起來的鏈條
- 作用:能很清楚的看見每個響應(yīng)者之間的聯(lián)系路操,并且可以讓一個事件多個對象處理疾渴。
- 響應(yīng)者對象:能處理事件的對象
事件傳遞的完整過程
先將事件對象由上往下傳遞(由父控件傳遞給子控件),找到最合適的控件來處理這個事件屯仗。
調(diào)用最合適控件的touches….方法
如果調(diào)用了[super touches….];就會將事件順著響應(yīng)者鏈條往上傳遞搞坝,傳遞給上一個響應(yīng)者
接著就會調(diào)用上一個響應(yīng)者的touches….方法
如何判斷上一個響應(yīng)者:
如果當(dāng)前這個view是控制器的view,那么控制器就是上一個響應(yīng)者
如果當(dāng)前這個view不是控制器的view,那么父控件就是上一個響應(yīng)者
響應(yīng)者鏈的事件傳遞過程
- 如果view的控制器存在,就傳遞給控制器魁袜;如果控制器不存在桩撮,則將其傳遞給它的父視圖
- 在視圖層次結(jié)構(gòu)的最頂級視圖,如果也不能處理收到的事件或消息峰弹,則其將事件或消息傳遞給window對象進(jìn)行處理
- 如果window對象也不處理店量,則其將事件或消息傳遞給UIApplication對象
- 如果UIApplication也不能處理該事件或消息,則將其丟棄
hitTest方法&pointInside方法
hitTest方法
- 當(dāng)事件傳遞給控件的時候鞠呈,就會調(diào)用控件的這個方法融师,去尋找最合適的view
- point:當(dāng)前的觸摸點,point這個點的坐標(biāo)系就是方法調(diào)用者
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
pointInside方法
- 作用:判斷當(dāng)前這個點在不在方法調(diào)用者(控件)上
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
hitTest:withEvent:的實現(xiàn)原理
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判斷當(dāng)前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判斷點在不在當(dāng)前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.從后往前遍歷自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
// 把當(dāng)前控件上的坐標(biāo)系轉(zhuǎn)換成子控件上的坐標(biāo)系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 尋找到最合適的view
return fitView;
}
}
// 循環(huán)結(jié)束,表示沒有比自己更合適的view
return self;
}