一.事件
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.重寫各個(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;
}