事件、事件響應(yīng)鏈端衰、手勢(shì)分析

一.事件

1.iOS三大事件包含觸摸事件叠洗,設(shè)備移動(dòng)事件,遠(yuǎn)程控制事件

2.iOS規(guī)定只有繼承UIResponder的類才能處理事件

AppDelegate,,UIWindow,UIViewController,UIView都是繼承它旅东,具有處理事件的能力灭抑。
AppDelegate處理UIApplication的事件,UIViewController處理它的View的事件抵代。

那么腾节,我們看一下UIResponder中部分方法

//觸摸事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//設(shè)備移動(dòng)事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event 
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event 
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event 
//遠(yuǎn)程控制事件
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event 

3.觸摸事件

一個(gè)觸摸點(diǎn)(一根手指)對(duì)應(yīng)一個(gè)UITouch對(duì)象,每一個(gè)UITouch會(huì)產(chǎn)生一個(gè)UIEvent的對(duì)象,包含事件類型等信息主守,所以參數(shù)是NSSet<UITouch *> *類型。

比如寫一個(gè)View隨手指拖動(dòng)的效果

//繼承UIView的ZSView,重寫touchmove方法
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch * touch=[touches anyObject];
    CGPoint point=[touch locationInView:self];
    CGPoint prepoint=[touch previousLocationInView:self];
    NSLog(@"%@-%@",NSStringFromCGPoint(point),NSStringFromCGPoint(prepoint));
    CGFloat offsentx=point.x-prepoint.x;
    CGFloat offsenty=point.y-prepoint.y;
    [self setTransform:CGAffineTransformTranslate(self.transform, offsentx, offsenty)];
    
    //如果傳nil,返回相對(duì)于當(dāng)前window的CGPoint
//    CGPoint point2=[touch locationInView:nil];
//    CGPoint prepoint2=[touch previousLocationInView:nil];
//    NSLog(@"2:%@-%@",NSStringFromCGPoint(point2),NSStringFromCGPoint(prepoint2));
    //NSLog(@"%s",__func__);
}

二.事件傳遞

觸摸事件的傳遞是從父控件傳遞到子控件,然后找到最合適的控件的過程;
UIApplication->UIWindow->UIView
如果父控件不接受事件榄融,所以子控件都不能接受事件;

如何找到最合適的控件處理事件呢参淫?
a.是否可以接受觸摸事件
b.是否在自己身上
c.重復(fù)往前遍歷子控件,重復(fù)ab
d.如果沒有符合條件的子控件愧杯,就自己處理

代碼實(shí)現(xiàn)就是hitTest的內(nèi)部處理

//當(dāng)前事件傳遞給當(dāng)前View涎才,當(dāng)前View的hitTest方法會(huì)被調(diào)用,去尋找最合適的UIView,返回值就是最合適的UIView,然后調(diào)用該UIView的touches方法
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //1.是否可以接受觸摸事件
    //2.是否在自己身上(pointInside方法)
    //3.從后往前遍歷子控件,重復(fù)12
    //4.如果沒有符合條件的子控件耍铜,就自己處理
    return [super hitTest:point withEvent:event];
}
//判斷點(diǎn)擊點(diǎn)在不在當(dāng)前View身上邑闺,在hitTest內(nèi)部調(diào)用(第2步)
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    //point表示當(dāng)前觸摸點(diǎn)
    return NO;
}

找?guī)讉€(gè)例子一下就能明白:


1.png

1.重寫各個(gè)View的touchesBegan

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%@--touches",NSStringFromClass(self.class));
}

結(jié)果是:點(diǎn)擊哪個(gè)View,哪個(gè)View的touchesBegan方法被調(diào)用(hitTest的自動(dòng)實(shí)現(xiàn))

2.重寫各個(gè)View的hitTest棕兼,但是僅僅調(diào)用父類方法(相當(dāng)于沒重寫)

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    return [super hitTest:point withEvent:event];
}

結(jié)果是:點(diǎn)擊哪個(gè)View陡舅,哪個(gè)View的touchesBegan方法被調(diào)用(hitTest的自動(dòng)實(shí)現(xiàn))

3.重寫紅色View的hitTest,返回self

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //如果事件傳遞到紅色View伴挚,返回最合適UIView為自己
    return self;
}

結(jié)果是:點(diǎn)擊灰色View和紅色View靶衍,都是紅色View的touchesBegan方法被調(diào)用
分析:點(diǎn)擊灰色View,滿足ab茎芋,再從后往前遍歷子View颅眶,遍歷到第一個(gè)紅色View,返回最優(yōu)響應(yīng)者是紅色View則停止遍歷田弥,點(diǎn)擊紅色也是同理涛酗。

4.重寫灰色View的hitTest,返回第一個(gè)自View

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    return self.subviews[0];
}

結(jié)果是:點(diǎn)擊灰色View及其子View偷厦,或者是藍(lán)色View都是綠色View的touchesBegan方法被調(diào)用
分析:window遍歷到vc.view的子控件時(shí)商叹,滿足ab,再從后遍歷子View沪哺,遍歷到第一個(gè)灰色View沈自,返回最優(yōu)響應(yīng)者是其中第一個(gè)子控件也就是綠色View,點(diǎn)擊其他View也是同理辜妓。

5.重寫綠色View的hitTest枯途,返回nil

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    return nil;
}

結(jié)果是:點(diǎn)擊綠色和黑色都會(huì)讓灰色View的touchesBegan方法被調(diào)用(穿透效果)
分析:點(diǎn)擊綠色和黑色時(shí),事件傳遞到綠色View籍滴,觸發(fā)hitTest酪夷,返回空,也就是沒和合適的View孽惰,所以回到上一層hitTest晚岭,也就是灰色View的hitTest,返回自己的View勋功。

6.重寫紅色View的pointInside坦报,返回YES

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return YES;
}

結(jié)果是:點(diǎn)擊灰色View及其子View,都是紅色View的touchesBegan方法被調(diào)用
分析:點(diǎn)擊灰色View狂鞋,滿足ab片择,倒敘遍歷子控件,遍歷到紅色View時(shí)骚揍,判斷點(diǎn)擊在紅色區(qū)域內(nèi)部字管,返回紅色View啰挪;點(diǎn)擊綠色或者黑色View,同樣會(huì)遍歷到紅色View時(shí)被阻止向下遍歷嘲叔,所以也是紅色ViewtouchesBegan方法被調(diào)用亡呵。

7.重寫綠色View的pointInside,返回YES

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return YES;
}

結(jié)果是:點(diǎn)擊灰色View硫戈,調(diào)用綠色View的touchesBegan锰什,其余都正常
分析:自己分析吧,懶得寫了掏愁。

響應(yīng)鏈

當(dāng)我們確定誰是那個(gè)最適合的UIView的時(shí)候歇由,怎么構(gòu)成一個(gè)響應(yīng)鏈呢。
重寫每個(gè)UIView的touchesBegan果港,調(diào)用父類touchesBegan方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%@--touches",NSStringFromClass(self.class));
    [super touchesBegan:touches withEvent:event];
}

結(jié)果是:
subView touches
superView touches
uiviewcontroller-view touches
window-touches
application-touches
也就是找到最合適處理事件的控件之后沦泌,調(diào)用touches,然后向上傳遞辛掠,這些響應(yīng)者連接在一起構(gòu)成了事件響應(yīng)鏈谢谦。如果subview重寫touches,就來到subview的touches萝衩,如果不重寫回挽,就來到superview的touches,如果superview不重寫,就來到uiviewcontroller的touches,如果uiviewcontroller不重寫猩谊,就來到window的touches千劈,如果window不重寫,就來到application(appdelegate)的touches牌捷,如果都不重寫墙牌,這個(gè)事件就拋棄了。

腦子里回想兩件事情暗甥,點(diǎn)擊APP上一個(gè)按鈕喜滨,如何找到被點(diǎn)擊按鈕的(命中測(cè)試),又如何再找到之后對(duì)事件做響應(yīng)處理的(響應(yīng)鏈傳遞)撤防,更容易幫你理解響應(yīng)鏈的整體概念虽风。

總結(jié)事件傳遞的完整過程:

a.先將事件由上向下(從父控件向子控件)傳遞,找到最合適處理事件的控件寄月。
b.調(diào)用最合適控件的touches..方法
c.如果調(diào)用[super touches]方法辜膝,就會(huì)將事件順著響應(yīng)鏈條向上傳遞,傳遞給上一個(gè)響應(yīng)者漾肮。
d.接著調(diào)用上一個(gè)響應(yīng)者的touches方法

三.應(yīng)用

最近發(fā)現(xiàn)觸及到事件響應(yīng)鏈的應(yīng)用場(chǎng)景的兩個(gè)問題厂抖,第一個(gè)是子View的大小超出父View的大小,但是還需要實(shí)現(xiàn)點(diǎn)擊子View的效果(默認(rèn)是不能點(diǎn)擊超出父View區(qū)域的)初橘;第二個(gè)問題是子View覆蓋在父View上验游,但是要實(shí)現(xiàn)穿透子View去響應(yīng)父View點(diǎn)擊事件,針對(duì)這兩個(gè)問題保檐,先說一下解決方案耕蝉,再看一下響應(yīng)鏈?zhǔn)侨绾巫龅降摹?/p>

1.子View超出父View的情況:

ZSView * view =[[ZSView alloc]initWithFrame:CGRectMake(200, 400, 50, 50)];
view.backgroundColor=[UIColor lightGrayColor];
[self.view addSubview:view];
[view setUserInteractionEnabled:YES];

//UIButton * btn = [[UIButton alloc]initWithFrame:CGRectMake(100, -100, 200, 200)];
UIButton * btn = [[UIButton alloc]initWithFrame:view.bounds];
CGRect frame = btn.frame;
frame.size.width=100;
btn.frame=frame;
btn.center=CGPointMake(CGRectGetMidX(view.bounds), -CGRectGetMidY(btn.bounds) - CGRectGetMidY(view.bounds)+10);
btn.backgroundColor=[UIColor orangeColor];
[btn addTarget:self action:@selector(clickbtn) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:btn];

重寫父View的pointInside方法

@implementation ZSView
/*
如果點(diǎn)擊區(qū)域在ZSView的范圍內(nèi),point為正數(shù)夜只,返回YES垒在,否則對(duì)應(yīng)x,y為負(fù)數(shù),返回NO扔亥。所以我們重寫該方法场躯,把超出部分的point返回YES就可以,或者把超出子View的區(qū)域轉(zhuǎn)換成自己View的坐標(biāo)系旅挤,也就point為正數(shù)了踢关。
*/
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    NSArray *subViews = self.subviews;
    if ([subViews count] > 0)
    {
        UIView *subview = [subViews objectAtIndex:0];
        if ([subview pointInside:[self convertPoint:point toView:subview] withEvent:event])
        {
            return YES;
        }
    }
    if (point.x > 0 && point.x < self.frame.size.width && point.y > 0 && point.y < self.frame.size.height)
    {
        return YES;
    }
    return NO;
}

2.穿透子View點(diǎn)擊父View

UIButton * btn  = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 300, 300)];
btn.backgroundColor=[UIColor redColor];
[btn addTarget:self action:@selector(clickBtn) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];  
ZSTableView * tableview = [[ZSTableView alloc]initWithFrame:CGRectMake(50, 50, 200, 200)];
[tableview setUserInteractionEnabled:NO];
tableview.backgroundColor=[UIColor lightGrayColor];
tableview.delegate=self;
tableview.dataSource=self;
[btn addSubview:tableview];

重寫子View(ZSTableView)的hitTest方法

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *hitView =[super hitTest:point withEvent:event];
    if(hitView == self){
        //自動(dòng)將事件傳遞到上一層
        return nil;
    }
    return hitView;   
}

還有同學(xué)舉到其他例子:

http://www.reibang.com/p/d8512dff2b3e

四.手勢(shì)

1.手勢(shì)分類

UITapGestureRecognizer 輕拍手勢(shì)
UISwipeGestureRecognizer 輕掃手勢(shì)
UILongPressGestureRecognizer 長按手勢(shì)
UIPanGestureRecognizer 平移手勢(shì)
UIPinchGestureRecognizer 捏合(縮放)手勢(shì)
UIRotationGestureRecognizer 旋轉(zhuǎn)手勢(shì)
UIScreenEdgePanGestureRecognizer 屏幕邊緣平移

基本使用就不介紹了,主要有一些常用手勢(shì)屬性粘茄。

2.UIPanGestureRecognizer签舞,UIPinchGestureRecognizer

讓UIView隨平移手勢(shì)拖動(dòng)和捏合手勢(shì)縮放

UIPanGestureRecognizer * pan=[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panView:)];
[self.testview addGestureRecognizer:pan];
    
UIPinchGestureRecognizer *pinch=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchView:)];
[self.testview addGestureRecognizer:pinch];

-(void)panView:(UIPanGestureRecognizer*)panGes
{
    //獲得平移偏移量(相對(duì)于手勢(shì)起始位置)
    CGPoint panPoint=[panGes translationInView:self.testview];
    //進(jìn)行偏移累計(jì)
    self.testview.transform=CGAffineTransformTranslate(self.testview.transform, panPoint.x, panPoint.y);
    //重置偏移量(因?yàn)槠揭破剖窍鄬?duì)于起始位置,如果不重置該值柒瓣,累計(jì)值就會(huì)疊加遞增)
    [panGes setTranslation:CGPointZero inView:self.testview];
}

-(void)pinchView:(UIPinchGestureRecognizer*)pinchGes
{
    //進(jìn)行縮放累計(jì)
    self.testview.transform=CGAffineTransformScale(self.testview.transform, pinchGes.scale, pinchGes.scale);
    //重置縮放范圍
    [pinchGes setScale:1.0];
}

3.處理手勢(shì)和UIView事件沖突

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    //如果點(diǎn)擊到GrayView,手勢(shì)不響應(yīng)事件
    if([touch.view isKindOfClass:GrayView.class])
    {
        return NO;
    }
    return YES;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末儒搭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子芙贫,更是在濱河造成了極大的恐慌搂鲫,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磺平,死亡現(xiàn)場(chǎng)離奇詭異魂仍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)褪秀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蓄诽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人媒吗,你說我怎么就攤上這事仑氛。” “怎么了闸英?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵锯岖,是天一觀的道長。 經(jīng)常有香客問我甫何,道長出吹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任辙喂,我火速辦了婚禮捶牢,結(jié)果婚禮上鸠珠,老公的妹妹穿的比我還像新娘。我一直安慰自己秋麸,他們只是感情好渐排,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灸蟆,像睡著了一般驯耻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炒考,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天可缚,我揣著相機(jī)與錄音,去河邊找鬼斋枢。 笑死帘靡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓤帚。 我是一名探鬼主播测柠,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼缘滥!你這毒婦竟也來了轰胁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤朝扼,失蹤者是張志新(化名)和其女友劉穎赃阀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擎颖,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榛斯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搂捧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驮俗。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖允跑,靈堂內(nèi)的尸體忽然破棺而出王凑,到底是詐尸還是另有隱情,我是刑警寧澤聋丝,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布索烹,位于F島的核電站,受9級(jí)特大地震影響弱睦,放射性物質(zhì)發(fā)生泄漏百姓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一况木、第九天 我趴在偏房一處隱蔽的房頂上張望垒拢。 院中可真熱鬧旬迹,春花似錦、人聲如沸求类。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仑嗅。三九已至,卻和暖如春张症,著一層夾襖步出監(jiān)牢的瞬間仓技,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工俗他, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脖捻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓兆衅,卻偏偏與公主長得像地沮,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子羡亩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353