一滋将、簡(jiǎn)介
在我們點(diǎn)擊屏幕的時(shí)候转绷,iOS系統(tǒng)會(huì)獲取“單擊”行為修然,把這個(gè)信息包裝成UITouch和UIEvent對(duì)象徊哑,然后在當(dāng)前運(yùn)行的程序匯總逐級(jí)查找這個(gè)事件的響應(yīng)對(duì)象袜刷,直到?jīng)]有響應(yīng)者響應(yīng),這個(gè)過(guò)程就是事件的響應(yīng)鏈莺丑。
事件傳遞過(guò)程如下圖:
二著蟹、響應(yīng)者對(duì)象
iOS中要響應(yīng)事件都必須繼承UIResponder墩蔓,且是對(duì)象,我們稱之為響應(yīng)者對(duì)象萧豆。
繼承UIResponder的有:
- UIApplication
- UIViewController
- UIView
2.1事件處理
UIResponder提供了以下方法處理用戶的事件奸披,分別對(duì)應(yīng)點(diǎn)擊,移動(dòng)涮雷,結(jié)束和取消阵面,其中取消方法只有在APP強(qiáng)制退出或者來(lái)電的時(shí)候才會(huì)被調(diào)用:
- 觸摸事件
//開始點(diǎn)擊
-(void)touchesBegin:(NSSet<UITouch *> *)touches withEvent(nullable UIEvent *)event;
//開始拖拽
-(void)touchedMoved:(NSSet<UITouch *>)touches withEvent:(nullable UIEvent *)evnet;
//點(diǎn)擊結(jié)束
-(void)touchedEnded:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;
//點(diǎn)擊取消
-(void)touchedCanceled:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;
NOTE:如果處理UIView的觸摸事件,必須要自定義UIView的子類洪鸭,在子類中重寫上面四個(gè)方法样刷。
如果是處理UIViewController的觸摸事件,直接重寫上面四個(gè)方法即可览爵。
- 加速事件
//開始加速
-(void)motionBegin:(NSSet<UITouch *> *)touches withEvent(nullable UIEvent *)event;
//點(diǎn)擊加速
-(void)motionEnded:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;
//加速取消
-(void)motionCanceled:(NSSet<UITouch *>)touched withEvent:(nullable UIEvent *)event;
- 遠(yuǎn)程控制事件
-(void)remoteControlReceviedWithEvent:(UIEvent *)evnet;
2.2觸摸事件詳解
在UIView上我們可以重寫這幾個(gè)方法進(jìn)行觸摸事件的回調(diào)處理置鼻,這些方法中都接收兩個(gè)參數(shù):UITouch對(duì)象的集合(點(diǎn)擊對(duì)象),UIEvent對(duì)象(事件對(duì)象)蜓竹。
2.2.1事件對(duì)象UIEvent
UIEvent表示用戶交互對(duì)象箕母,用UIEventTpye類型的屬性可以表示單簽的響應(yīng)事件類型:
@property(nonatomic,readonly) UIEventType type
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches,//多點(diǎn)觸控
UIEventTypeMotion, //移動(dòng)拖拽
UIEventTypeRemoteControl,//遠(yuǎn)程操作
UIEventTypePresses //3D touch
};
2.2.2點(diǎn)擊對(duì)象UITouch
對(duì)象:
- UITouch表示單個(gè)點(diǎn)擊,當(dāng)用戶用手指觸摸屏幕時(shí)俱济,會(huì)創(chuàng)建一個(gè)或多個(gè)與手指相關(guān)的UITouch對(duì)象(一根手指對(duì)應(yīng)一個(gè)UITouch對(duì)象)嘶是。
- 如果兩個(gè)手指同時(shí)觸摸一個(gè)view,那么view只會(huì)調(diào)用一次toucheBegan方法蛛碌,touches包含兩個(gè)UITouch對(duì)象俊啼。
- 兩個(gè)手指一前一后點(diǎn)擊屏幕,會(huì)調(diào)用兩次左医,每次調(diào)用的方法里的touches包含一個(gè)UITouch對(duì)象授帕。
作用:
- 保存跟手指相關(guān)的信息,比如觸摸的位置浮梢,時(shí)間跛十,階段
- 當(dāng)手指移動(dòng)的時(shí)候,系統(tǒng)會(huì)更新改UITouch對(duì)象的信息秕硝。
- 當(dāng)手指離開屏幕的時(shí)候芥映,系統(tǒng)會(huì)銷毀該UITouch對(duì)象。
UITouch屬性:
- 當(dāng)前點(diǎn)擊狀態(tài)
用UITouchPhase屬性來(lái)表示當(dāng)前點(diǎn)擊的狀態(tài)远豺,這些狀態(tài)對(duì)應(yīng)奈偏,開始、移動(dòng)躯护、停止不動(dòng)惊来、結(jié)束、取消五個(gè)狀態(tài)
@property(nonatomic,readonly) UITouchPhase phase;
typedef NS_ENUM(UITouchPhase,UITouchPhase{
NSTouchPhaseBegin,
NSTouchPhaseMoved,
NSTouchPhaseStationary,
NSTouchPhaseEnded,
NSTouchPhaseCanceled,
};
- 觸摸產(chǎn)生時(shí)所處的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
- 觸摸產(chǎn)生時(shí)所處的視圖
@property(nonatomic,readonly,retain) UIView *view
;
- 點(diǎn)按屏幕的次數(shù)
可以根據(jù)tapCount判斷單擊棺滞、雙擊或更多的點(diǎn)擊
@property(nonatomic,readonly) NSUInteger tapCount;
- 觸摸事件產(chǎn)生或變化時(shí)的時(shí)間裁蚁,單位是秒
@property(nonatomic,readonly) NSTimeInterval timestamp;
UITouch方法:
- 通過(guò)以下方法可以獲取用戶點(diǎn)擊的位置:
//獲取點(diǎn)擊坐標(biāo)點(diǎn)
-(CGPoint) locationInView:(UIView *)view;
//獲取上個(gè)點(diǎn)擊位置的坐標(biāo)點(diǎn)
-(CGPoint) previousLocationInView:(UIView *)view;
2.3 代碼實(shí)現(xiàn)拖拽UIView
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//獲取當(dāng)前UITouch對(duì)象
UITouch *touch = [touches anyObject];
//當(dāng)前手指所指位置
CGPoint currentP = [touch locationInView:self];
//之前手指所在位置
CGPoint previousP = [touch previousLocationInView:self];
//相對(duì)于之前的位置x,y方向的偏移量
CGFloat offsetX = currentP.x - previousP.x;
CGFloat offsetY = currentP.y - previousP.y;
//在之前的偏移的基礎(chǔ)上再做移動(dòng)
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
三矢渊、事件的產(chǎn)生和傳遞
發(fā)生觸摸事件A后,系統(tǒng)會(huì)將A加到UIApplication管理的事件隊(duì)列中枉证,再由UIApplication把事件分發(fā)下去矮男,通常,會(huì)先把A事件發(fā)送給keyWindow室谚。keyWindow會(huì)在視圖層次中找出一個(gè)最適合的視圖來(lái)處理觸摸事件毡鉴,找到合適的視圖后,會(huì)調(diào)用該視圖的touched方法來(lái)做具體的操作秒赤。通常的順序?yàn)椋?/p>
UIApplicaton -> UIWindow -> UIViewControleler -> UIView -> subView
note:如果父視圖不能接受觸摸事件眨补,那么子視圖就不可能收到觸摸事件
3.1查找最合適的控件處理事件
- 判斷keyWindow是否能接受觸摸事件
- 判斷觸摸點(diǎn)是否在keyWindow的bounds里面
- 子視圖數(shù)組(subviews)從后往前遍歷,也就是從視圖的頂部往底部遍歷倒脓,重復(fù)判斷:1撑螺、是否能接受觸摸事件(是否是UIResponder的子類)2、觸摸點(diǎn)是否在自己的bounds里面崎弃。
- 如果找到了符合條件view1甘晤,就好把事件傳遞view1,再遍歷view1的子控件饲做,再次判斷线婚。
- 如果沒有符合條件的子控件,那么自己就是最合適的控件盆均。
note:UIImageView的交互默認(rèn)是關(guān)閉的塞弊,如果要往上添加button或者手勢(shì)需要先將交互打開
圖示:
3.2兩個(gè)底層方法
在查找最合適的view的過(guò)程中用到了兩個(gè)最重要的方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
3.2.1 hitTest:withEvent:
只要事件一傳遞給一個(gè)控件,這個(gè)控件就會(huì)調(diào)用自己的hitTest:withEvent:方法泪姨,用于尋找并返回最合適的view游沿, 它不管這個(gè)控件能不能處理事件也不管點(diǎn)是否在view上,事件都會(huì)先傳給這view再調(diào)用這個(gè)view的hitTest方法肮砾。不管點(diǎn)擊哪里诀黍,最合適的view都是hitTest返回的那個(gè)view。
利用這個(gè)特性可以攔截事件的處理:
事件傳遞給誰(shuí)就會(huì)調(diào)用這個(gè)view的hitTest:withEvent:方法仗处,如果返回nil眯勾,那么該方法的控件本身和子控件不是最合適的view,那么最合適的view就是該控件的父控件婆誓。
** tip:想讓a成為最合適的view就重寫a的父控件b的hitTest:withEvent:方法吃环,或者自己的hitTest:withEvent:方法返回 self。建議采用第一種**
攔截代碼實(shí)現(xiàn):
如果沒有攔截最合適的view的時(shí)候洋幻,DView是可以拖動(dòng)的郁轻,但是攔截之后,由于最合適的view變成了BView鞋屈,所以DView的touched方法不會(huì)實(shí)現(xiàn)
#implementation Aview
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
for (UIView *v in self.subviews) {
if ([v isKindOfClass:[BView class]]) {
return v;
}
}
return self;
}
特殊情況:
- 誰(shuí)都不能處理事件范咨,窗口也不能處理故觅。
重寫window的hitTest:withEvent:方法return nil
- 只能由窗口處理事件厂庇。
控制器的view的hitTest:withEvent:方法return nil或者window的hitTest:withEvent:方法return self
- return nil的含義:
調(diào)用當(dāng)前hitTest:withEvent:方法的view不是合適的view渠啊,子控件也不是合適的view。如果同級(jí)的兄弟控件也沒有合適的view权旷,那么最合適的view就是父控件替蛉。
hitTest:withEvent:中return nil的意思是調(diào)用當(dāng)前hitTest:withEvent:方法的view不是合適的view,子控件也不是合適的view拄氯,那么就去判斷上一個(gè)調(diào)用hittest的兄弟控件是否有合適的view躲查,如果同級(jí)的兄弟控件也沒有合適的view,那么最合適的view就是父控件译柏。
3.2.2 pointInside:withEvent
pointInside:withEvent:方法判斷點(diǎn)在不在當(dāng)前view上(方法調(diào)用者的坐標(biāo)系上)如果返回YES镣煮,代表點(diǎn)在方法調(diào)用者的坐標(biāo)系上;返回NO代表點(diǎn)不在方法調(diào)用者的坐標(biāo)系上,那么方法調(diào)用者也就不能處理事件鄙麦。
3.3事件響應(yīng)的傳遞
事件響應(yīng)的傳遞(需要做的操作)與事件的傳遞(查找最佳響應(yīng)者)的過(guò)程相反:
view -> 父view -> viewController -> keyWindow -> application
用代碼驗(yàn)證:
#implementation Dview
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UIResponder *nextResponder = [self nextResponder];
NSMutableString *prefix = @"".mutableCopy;
while (nextResponder) {
NSLog(@"%@%@\n",prefix,[nextResponder class]);
[prefix stringByAppendingString:@"--"];
nextResponder = [nextResponder nextResponder];
}
NSLog(@"ddd");
}
從打印處理的信息可以看出事件響應(yīng)的傳遞為:
2016-03-09 17:30:41.092 responder[12747:3088146] CView
2016-03-09 17:30:41.092 responder[12747:3088146] AView
2016-03-09 17:30:41.093 responder[12747:3088146] UIView
2016-03-09 17:30:41.093 responder[12747:3088146] ViewController
2016-03-09 17:30:41.093 responder[12747:3088146] UIWindow
2016-03-09 17:30:41.093 responder[12747:3088146] UIApplication
2016-03-09 17:30:41.094 responder[12747:3088146] AppDelegate
3.4一個(gè)事件多個(gè)響應(yīng)
在DView的touched方法中做操作處理后調(diào)用super的touched方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"ddd");
[super touchesBegan:touches withEvent:event];
}
在CView的touched方法中做操作處理后調(diào)用super的touched方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"ccc");
[super touchesBegan:touches withEvent:event];
}
AView中以此類推!