iOS的事件可以分為三類:觸摸事件,加速計(jì)事件,遠(yuǎn)程控制事件
iOS中不是任何對(duì)象都能處理對(duì)象,只有繼承了UIResponder的對(duì)象才能接收并處理事件. ---->響應(yīng)者對(duì)象
我們可以觀察到 UIView就是繼承于UIResponder,所以所有可看到的控件都是可以接收到事件的.
UIApplication,UIViewController,UIView都繼承自UIResponder,因此它們都是響應(yīng)者對(duì)象, 都能夠就收并處理事件
UIResponder內(nèi)部提供了以下方法專門用來事件的處理
1.觸摸事件:
--->我們經(jīng)常用來測試用的方法:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
觸摸事件開始
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
拖拽事件
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
觸摸結(jié)束事件
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
觸摸事件被打斷
2.加速計(jì)事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
加速計(jì)事件開始
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
加速計(jì)事件結(jié)束
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
加速計(jì)事件被打斷
3.遠(yuǎn)程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
接收遠(yuǎn)程控制事件
UIView的觸摸事件處理
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
觸摸事件開始[一根或者多根手指開始觸摸view,系統(tǒng)會(huì)自動(dòng)調(diào)用]
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
拖拽事件[在view上移動(dòng)的時(shí)候,調(diào)用這方法]
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
觸摸結(jié)束事件[手指離開view,調(diào)用這個(gè)方法]
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
觸摸事件被打斷[在觸摸結(jié)束前,被系統(tǒng)事件打斷,(如打電話,短信等等),系統(tǒng)自動(dòng)調(diào)用]
注意:touches中存放的都是:UITouch對(duì)象.
UITouch的屬性:
1.觸摸產(chǎn)生時(shí)所處的窗口
@property(nonatomic,readonly,retain)UIWindow *window;
2.觸摸產(chǎn)生時(shí)所處的視圖
@property(nonatomic,readonly,retain)UIView *view;
3.短時(shí)間內(nèi)點(diǎn)按屏幕的次數(shù),可以根據(jù)tapCount判斷單擊驰徊、雙擊或更多的點(diǎn)擊[一般不常用]
@property(nonatomic,readonly)NSUInteger tapCount;
4.記錄了觸摸事件產(chǎn)生或變化時(shí)的時(shí)間笤闯,單位是秒
@property(nonatomic,readonly)NSTimeInterval timestamp;
5.當(dāng)前觸摸事件所處的狀態(tài)
@property(nonatomic,readonly)UITouchPhase phase;
UITouch的方法
- (CGPoint)locationInView:(UIView *)view
1.返回值表示觸摸在view的位置
2.如果傳入的view為nil的話,返回的觸摸點(diǎn)在UIWindow的位置
- (CGPoint)previousLocationInView:(UIView *)view
記錄前一個(gè)觸摸點(diǎn)的位置
代碼實(shí)現(xiàn)簡單的拖拽,來解釋這個(gè)屬性和方法
// 驗(yàn)證點(diǎn)擊事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
}
// 驗(yàn)證拖拽移動(dòng)事件
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
// 得到當(dāng)前點(diǎn)按的位置,是針對(duì)與self(紅色的view)來說的
CGPoint curP = [touch locationInView:self];
// 得到前一次點(diǎn)得位置
CGPoint lastP = [touch previousLocationInView:self];
// NSLog(@"%s",__func__);
// NSLog(@"%@",touch);
NSLog(@"%@-----%@",NSStringFromCGPoint(curP),NSStringFromCGPoint(lastP));
// 計(jì)算 x軸方向的偏移量(都是針對(duì)于上一次棍厂,也就是針對(duì)與上一次的改變)
CGFloat offsetX = curP.x - lastP.x;
// 計(jì)算 y軸的偏移量
CGFloat offsetY = curP.y - lastP.y;
// 在原有的基礎(chǔ)上進(jìn)行平移
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
// 點(diǎn)擊結(jié)束后調(diào)用
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
}
// 程序被迫中斷時(shí)調(diào)用颗味,如電話打進(jìn)了。
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%s",__func__);
}
UIEvent
每產(chǎn)生一個(gè)事件,就會(huì)產(chǎn)生一個(gè)UIEvent對(duì)象, 內(nèi)部記錄著事件產(chǎn)生的時(shí)刻和類型
常見屬性
1.事件類型
@property(nonatomic,readonly)UIEventType type;
@property(nonatomic,readonly)UIEventSubtype subtype;
2.事件產(chǎn)生的時(shí)間
@property(nonatomic,readonly)NSTimeInterval timestamp;
lUIEvent還提供了相應(yīng)的方法可以獲得在某個(gè)view上面的觸摸對(duì)象(UITouch)
touches和event參數(shù)
一次完整的觸摸過程牺弹,會(huì)經(jīng)歷3個(gè)狀態(tài):
1觸摸開始:
- (void)touchesBegan:(NSSet *)touches withEvent (UIEvent *)event
2.觸摸移動(dòng):
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
3.觸摸結(jié)束:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
4.觸摸取消(可能會(huì)經(jīng)歷):
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
4個(gè)觸摸事件處理方法中浦马,都有NSSet *touches和UIEvent *event兩個(gè)參數(shù)
5.一次完整的觸摸過程中,只會(huì)產(chǎn)生一個(gè)事件對(duì)象例驹,4個(gè)觸摸方法都是同一個(gè)event參數(shù)
5.1如果兩根手指同時(shí)觸摸一個(gè)view捐韩,那么view只會(huì)調(diào)用一次touchesBegan:withEvent:方法退唠,touches參數(shù)中裝著2個(gè)UITouch對(duì)象
5.2如果這兩根手指一前一后分開觸摸同一個(gè)view鹃锈,那么view會(huì)分別調(diào)用2次touchesBegan:withEvent:方法,并且每次調(diào)用時(shí)的touches參數(shù)中只包含一個(gè)UITouch對(duì)象
5.3根據(jù)touches中UITouch的個(gè)數(shù)可以判斷出是單點(diǎn)觸摸還是多點(diǎn)觸摸
事件的產(chǎn)生和傳遞
1.發(fā)生觸摸事件后,系統(tǒng)會(huì)將該事件加入一個(gè)UIApplication管理的事件隊(duì)列中
2.UIApplication會(huì)從事件隊(duì)列中取出最前面的事件,并將事件分發(fā)下去進(jìn)行處理.通常先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)
3.主窗口會(huì)在視圖層次結(jié)構(gòu)中找到一個(gè)最合適的視圖來處理觸摸事件., 這也是整個(gè)事件處理過程中的第一步,也是最重要的一部
4.找到合適的視圖控件后,就會(huì)調(diào)用視圖控件的touches方法來作具體的事件處理
5.如果父控件不能接收觸摸事件瞧预,那么子控件就不可能接收到觸摸事件
那么如何找到最合適的控件來處理事件?
1.先查看自己是否能接收觸摸事件
2.觸摸點(diǎn)是否在自己身上
3.從后往前
遍歷子控件,重復(fù)1,2步驟
4.如果沒有符合條件的子控件,那么就是自己最適合處理
圖形說明:
觸摸事件的傳遞;
1.點(diǎn)擊了綠色的view
UIApplication-->UIWidow(keyWindow)-->白色的view-->綠色
2.點(diǎn)擊了藍(lán)色view
UIApplication-->UIWindow-->白色-->橙色-->藍(lán)色
3.點(diǎn)擊了黃色view
UIApplication-->UIWindow-->白色-->橙色-->藍(lán)色-->黃色
UIView不接收觸摸事件的三種情況:
1.不接收用戶交互:
userInteractionEnabled = NO
2.隱藏
hidden = YES
3.透明度(近乎透明)
alpha = 0.0~0.01之間
提示: UIImageView的userInteractionEnabled默認(rèn)為NO,因此UIImageView以及它的子控件默認(rèn)是不接收觸摸事件的
模擬蘋果來完成它的底部實(shí)現(xiàn)過程,
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"%@----%s",[self class],__func__);
}
// ponit是方法調(diào)用者坐標(biāo)系上的觸摸點(diǎn)位置--->當(dāng)觸摸事件產(chǎn)生時(shí),會(huì)調(diào)用這個(gè)方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判斷能否接收觸摸事件 (繼承與uirespond)(隱藏屎债,交互仅政,透明度)
if (self.hidden == YES || self.userInteractionEnabled == NO || self.alpha < 0.01 )
return nil;
// 觸摸點(diǎn)的位置在不在控件上
if (![self pointInside:point withEvent:event]) return nil;
// 如果在控件上,則遍歷控件的子控件(由后往前)
int count = (int)self.subviews.count;
for (int i = count - 1; i >= 0; i--) {
// 取出最前面的子控件
UIView *childView = self.subviews[i];
// 將觸摸點(diǎn)的位置盆驹,轉(zhuǎn)換成該控件的坐標(biāo)系上的點(diǎn)
CGPoint childP = [self convertPoint:point toView:childView];
// 用點(diǎn)的位置圆丹,以及事件再次判斷
UIView *fitView = [childView hitTest:childP withEvent:event];
// 如果找到合適的view,則返回合適的view
if (fitView) {
return fitView;
}
}
// 沒有合適的view 則返回自己躯喇。
return self;
}
控制臺(tái)的打印
既然已經(jīng)知道了,事件的傳遞過程,那么我們完全可能做出一些特殊的行為,去攔截操作, 一下兩個(gè)方法都可以進(jìn)行事件的攔截
// 判斷觸摸點(diǎn)在view上,是否能夠接收事件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// 將點(diǎn)得坐標(biāo)系改變
CGPoint blueP = [self convertPoint:point toView:_blueBtn];
// 判斷是否在藍(lán)色按鈕上 辫封,
if ([self.blueBtn pointInside:blueP withEvent:event]) {
// 如果在,則不讓它進(jìn)行相應(yīng)廉丽。
return NO;
}
// 判斷繼續(xù)(由系統(tǒng)自行判斷)
return [super pointInside:point withEvent:event];
}
// 重寫hittest 方法倦微,可以攔截監(jiān)聽。 或者說正压,想讓誰聽欣福,誰就聽
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 將點(diǎn)得坐標(biāo)系改變
CGPoint blueP = [self convertPoint:point toView:_blueBtn];
// 判斷是否在藍(lán)色按鈕上 ,
if ([self.blueBtn pointInside:blueP withEvent:event]) {
return _blueBtn;
}
// 繼續(xù)判斷(由系統(tǒng)內(nèi)部做決定)
return [super hitTest:point withEvent:
event];
//return self; 不能返回self焦履,這樣會(huì)造成拓劝,無論條件是否符合(能不能監(jiān)聽,點(diǎn)有沒有在它上面)嘉裤,點(diǎn)都在yellow上
}
層級(jí)結(jié)構(gòu)就是
黃色的view
在藍(lán)色的按鈕
上面按照,事件傳遞的規(guī)律,點(diǎn)擊黃色區(qū)域應(yīng)該是view做出響應(yīng),當(dāng)然點(diǎn)擊覆蓋在按鈕上的區(qū)域,也應(yīng)該是view作出相應(yīng),但是由于我們將事件傳遞做出了改變,所以發(fā)生了事件攔截現(xiàn)象. 按鈕能夠監(jiān)聽點(diǎn)擊事件
觸摸事件處理的詳細(xì)過程
1.用戶點(diǎn)擊了屏幕后產(chǎn)生一個(gè)觸摸事件,經(jīng)過一系列傳遞以后,會(huì)找到最合適的視圖控件來處理這個(gè)事件
2.找到最合適的視圖空間后,就會(huì)調(diào)用控件的touches方法來做具體事件處理
3.這些touches方法的默認(rèn)做法是將事件順著響應(yīng)鏈條
向上傳遞,將事件交給上一個(gè)響應(yīng)者進(jìn)行處理
響應(yīng)者鏈條示意圖
1.響應(yīng)者鏈條:是由多個(gè)響應(yīng)者對(duì)象連接起來的鏈條
2.作用:能很清楚的看見每個(gè)響應(yīng)者之間的對(duì)象,并且可以讓一個(gè)事件多個(gè)對(duì)象處理
3.響應(yīng)者對(duì)象:能處理事件的對(duì)象
事件傳遞的完整過程
1.先將事件對(duì)象由上往下傳遞(由父控件傳遞給子控件),找到最合適的控件來處理這個(gè)事件
2.調(diào)用最合適控件的touches方法
3.如果調(diào)用了[super touches...];就會(huì)將事件順著響應(yīng)者鏈條往上傳遞,傳遞給上一個(gè)響應(yīng)者
4.接著就會(huì)調(diào)用上一個(gè)響應(yīng)者的touches...方法
如何判斷上一個(gè)響應(yīng)者
1.如果當(dāng)前這個(gè)view是控制器的view,那么控制器就是上一個(gè)響應(yīng)者
2.如果當(dāng)前這個(gè)view不是控制器的view,那么父控件就是上一個(gè)響應(yīng)者
響應(yīng)者鏈的事件傳遞過程
1.如果view的控制器存在郑临,就傳遞給控制器;如果控制器不存在屑宠,則將其傳遞給它的父視圖
2.在視圖層次結(jié)構(gòu)的最頂級(jí)視圖牧抵,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對(duì)象進(jìn)行處理
3.如果window對(duì)象也不處理侨把,則其將事件或消息傳遞給UIApplication對(duì)象
4.如果UIApplication也不能處理該事件或消息犀变,則將其丟棄
一個(gè)小應(yīng)用來講解事件的攔截
// 監(jiān)聽按鈕的點(diǎn)擊
- (IBAction)PopClick:(PopButton *)sender {
UIButton *chatView = [UIButton buttonWithType:UIButtonTypeCustom];
[chatView setImage:[UIImage imageNamed:@"對(duì)話框"] forState:UIControlStateNormal];
[chatView setImage:[UIImage imageNamed:@"小孩"] forState:UIControlStateHighlighted];
chatView.bounds = CGRectMake(0, -200, 200, 200);
// 它的父控件為基準(zhǔn)
chatView.center = CGPointMake(100, -100);
// 將對(duì)話框加入父控件中 ,超出的部分仍然可以顯示秋柄, 只是不能點(diǎn)擊
[sender addSubview:chatView];
sender.chatView = chatView;
}
///自定義按鈕,來實(shí)現(xiàn)變態(tài)攔截功能
// 外界提供_chatView用來接收新的對(duì)話框
// 重寫hitTest方法获枝,讓能點(diǎn)擊對(duì)話框。 只要攔截點(diǎn)擊就行了
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 將點(diǎn)得坐標(biāo)系轉(zhuǎn)換
CGPoint chatP =[self convertPoint:point toView:_chatView];
// 判斷點(diǎn)是否在 控件上
if ([_chatView pointInside:chatP withEvent:event]) {
return _chatView;
}
// 若不在骇笔,執(zhí)行以前的方式
return [super hitTest:point withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint curOffset = [touch locationInView:self];
CGPoint lastOffset = [touch previousLocationInView:self];
CGFloat offsetX = curOffset.x -lastOffset.x;
CGFloat offsetY = curOffset.y - lastOffset.y;
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}