UITouch官方教程
UIGesturereCognizer官方教程
参考教程
事件处理demo
IOS当中常用的事件分为三种:
- 触摸事件
- 加速计事件
- 远程控制事件
触摸事件
什么是响应者对象?
继承了UIResponds的对象我们称它为响应者对象UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。
为什么说继承了UIResponder就能够处理事件?
因为UIResponder内部提供了以下方法来处理事件
触摸事件会调用以下方法:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
加速计事件会调用:
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
远程控制事件会调用:
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
如何监听UIView的触摸事件
想要监听UIViiew的触摸事件,首先第一步要自定义UIView,因为只有实现了UIResponder的事件方法才能够监听事件。
-
UIView的触摸事件主要有:
一根或者多根手指开始触摸view,系统会自动调用view的下面方法.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event一根或者多根手指在view上移动时,系统会自动调用view的下面方法 (随着手指的移动,会持续调用该方法,也就是说这个方法会调用很多次) - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 一根或者多根手指离开view,系统会自动调用view的下面方法 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
参数说明:
touches:
(1)touches中存放的都是UITouch对象,它是一个NSSet集合。
(2)UITouch对象它就是用来保存手指相关联的信息。包括位置、时间、阶段等信息.
(3)每一个手指对应着一个UITouch对象。
(4)这个UITouch是系统自动帮我们创建的,当手指移动时,系统会更新同一个UITouch对象,使它能够一直保存该手指在的触摸位置.
(5)通过获取UITouch属性,我们可以获得触摸产生时所处的窗口、触摸的View、时间、点击的次数等,这些都可以在通过UITouch获取。
(6)通过UITouch提供的方法获取当前手指所在的点,以及上一个手指所在的点。
// 取当前手指所在的点
- (CGPoint)locationInView:(UIView *)view;
// 获取上一个触摸点的位置.
- (CGPoint)previousLocationInView:(UIView *)view;
event:
- 每产生一个事件,就会产生一个UIEvent对象
- UIEvent:称为事件对象,记录事件产生的时刻和类型
- 一次完整的触摸过程,会经历3个状态:
触摸开始:- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
触摸移动:- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
触摸结束:- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
触摸取消(可能会经历):- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数。如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象。如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象。
事件的产生和传递
- 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。
- UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
- 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
- 找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
touchesBegan…
touchesMoved…
touchedEnded…
具体说个例子:
触摸事件的传递是从父控件传递到子控件
点击了绿色的view:
UIApplication -> UIWindow -> 白色 -> 绿色
点击了蓝色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色
点击了黄色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色 -> 黄色
事件传递的完整过程
大概可以理解为:用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件,找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理,那这些touches方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理。
事件传递的完整过程:
(1) 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。
(2)调用最合适控件的touches….方法
(3)如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者
(4)接着就会调用上一个响应者的touches….方法寻找上一个响应者 (如图响应者链条图.png)
(1)如果当前的View是控制器的View,那么控制器就是上一个响应者.
(2)如果当前的View不是控制器的View,那么它的父控件就是上一个响应者.
(3)在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
(4)如果window对象也不处理,则其将事件或消息传递给UIApplication对象
(5)如果UIApplication也不能处理该事件或消息,则将其丢弃
关于事件相关的注意点:
- 一个控件什么情况下不能够接收事件.
1.不接收用户交互时不能够处理事件
userInteractionEnabled = NO
2.当一个控件隐藏的时候不能够接收事件
Hidden = YES的时候
3.当一个控件为透明白时候也不能够接收事件
注意:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的。
- 事件传递过程中,需要寻找到最适合的View来执行事件。
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件.
那如何找到最合适的View呢?
1.先判断自己是否能够接收触摸事件,如果能再继续往下判断,
2.再判断触摸的当前点在不在自己的身上.
3.如果在自己身上,它会从后往前遍历子控件,遍历出每一个子控件后,重复前面的两个步骤.
4.如果没有符合条件的子控件,那么它自己就是最适合的View.
- hitTest方法与PointInside方法
作用:寻找最适合的View
参数:当前手指所在的点.产生的事件
返回值:返回谁, 谁就是最适合的View.
什么时候用调用:只要一个事件,传递给一个控件时, 就会调用这个控件的hitTest方法
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
作用:判断point在不在方法调用者上
point:必须是方法调用者的坐标系
什么时候调用:hitTest方法底层会调用这个方法,判断点在不在控件上.
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
return YES;
}
- hitTest底层实现:
1.判断当前能不能接收事件
if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01)
2.判断触摸点在不在当前的控件上
[self pointInside:point withEvent:event];
3.从后往前遍历自己的子控件
int count = (int)self.subviews.count;
for (int i = count - 1; i >= 0;i-- ) {
UIView *childV = self.subviews[i];
// 把当前坐标系上的点转换成子控件坐标系上的点.
CGPoint childP = [self convertPoint:point toView:childV];
// 判断自己的子控件是不是最适合的View
UIView *fitView = [childV hitTest:childP withEvent:event];
// 如果子控件是最适拿的View,直接返回
if (fitView) {
return fitView;
}
}
4.自己就是最适合的View
UIGestureRecognizer手势
通过touches方法监听view触摸事件有以下几个缺点
1.必须得自定义view,在自定义的View当中去实现touches方法.
2.由于是在view内部的touches方法中监听触摸事件,因此默认情况下,无法让其他外界对象监听view的触摸事件
3.不容易区分用户的具体手势行为(不容易区分是长按手势,还是缩放手势)这些等.
iOS 3.2之后,苹果推出了手势识别功能UIGestureRecognizer
利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势
UIGestureRecognizer是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势
注意手势有以下几种:
UITapGestureRecognizer(敲击)
UIPinchGestureRecognizer(捏合,用于缩放)
UIPanGestureRecognizer(拖拽)
UISwipeGestureRecognizer(轻扫)
UIRotationGestureRecognizer(旋转)
UILongPressGestureRecognizer(长按)
手势识别的基本属性:
手势识别的状态
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
// 没有触摸事件发生,所有手势识别的默认状态
UIGestureRecognizerStatePossible,
// 一个手势已经开始但尚未改变或者完成时
UIGestureRecognizerStateBegan,
// 手势状态改变
UIGestureRecognizerStateChanged,
// 手势完成
UIGestureRecognizerStateEnded,
// 手势取消,恢复至Possible状态
UIGestureRecognizerStateCancelled,
// 手势失败,恢复至Possible状态
UIGestureRecognizerStateFailed,
// 识别到手势识别
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};
手势的使用:
-
添加点按手势
创建手势
Target:当哪对象要坚听手势
action:手势发生时调用的方法
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tap)];
手势也可以设置代理
tap.delegate = self;
添加手势
[self.imageV addGestureRecognizer:tap];以下为手势代理方法: 是否允许接收手指. 当返回为yes的时候,表示能够接收手指,当为No的时候,表示不能够接收手指,也就是不能够接收事件. -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch: (UITouch *)touch{ 获取当前手指所在的点 CGPoint curP = [touch locationInView:self.imageV]; if (curP.x > self.imageV.bounds.size.width * 0.5) { 在右边,返回NO return NO; }else{ 在左边,返回yes, return YES; } } 当手指开始点击时调用 -(void)tap{ NSLog(@"%s",__func__); }
2.添加长按手势
UILongPressGestureRecognizer *longP = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(longPress:)];
添加手势
[self.imageV addGestureRecognizer:longP];当手指长按时调用 注意,长按手势会调用多次,当开始长按时会调用,当长按松开时会调用,当长按移动时, 也会调用. 一般我们都是在长按刚开始时做事情,所以要判断它的状态. 这个状态是保存的当前的手势当中, 所以要把当前的长按手势传进来, 来判断当前手势的状态. - (void)longPress:(UILongPressGestureRecognizer *)longP{ 手势的状态是一个枚举UIGestureRecognizerState,可以进入头文件当中查看. if (longP.state == UIGestureRecognizerStateBegan) { NSLog(@"开始长按时调用"); }else if(longP.state == UIGestureRecognizerStateChanged){ 会持续调用 NSLog(@"当长按拖动时调用"); }else if(longP.state == UIGestureRecognizerStateEnded){ NSLog(@"当长按松手指松开进调用"); } }
3.轻扫手势
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)]; 注意:轻扫手势默认轻扫的方向是往右轻扫,可以去手动修改轻扫的方向 一个手势只能对象一个方向,想要支持多个方向的轻扫,要添加多个轻扫手势 swipe.direction = UISwipeGestureRecognizerDirectionLeft; 添加手势 [self.imageV addGestureRecognizer:swipe]; 再添加一个轻扫手势 轻扫手势 UISwipeGestureRecognizer *swipe2 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)]; 注意:轻扫手势默认轻扫的方向是往右轻扫,可以去手动修改轻扫的方向 一个手势只能对象一个方向,想要支持多个方向的轻扫,要添加多个轻扫手势 swipe2.direction = UISwipeGestureRecognizerDirectionDown; 添加手势 [self.imageV addGestureRecognizer:swipe2];
4.拖动
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(pan:)];
添加手势
[self.imageV addGestureRecognizer:pan];
实现手势方法
手指在屏幕上移动进调用
- (void)pan:(UIPanGestureRecognizer *)pan{
获取当前手指移动的偏移量
CGPoint transP = [pan translationInView:self.imageV];
NSLog(@"%@",NSStringFromCGPoint(transP));
Make它会清空上一次的形变.
self.imageV.transform = CGAffineTransformMakeTranslation(transP.x, transP.y);
self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform,
transP.x, transP.y);
复位,相对于上一次.
[pan setTranslation:CGPointZero inView:self.imageV];
}
2.旋转
添加旋转手势
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc]
initWithTarget:self action:@selector(rotation:)];
设置代理,设置代理的目的就让它能够同时支持旋转跟缩放
rotation.delegate = self;
添加手势
[self.imageV addGestureRecognizer:rotation];
当旋转时调用
- (void)rotation:(UIRotationGestureRecognizer *)rotation{
旋转也是相对于上一次
self.imageV.transform = CGAffineTransformRotate(self.imageV.transform,
rotation.rotation);
设置代理,设置代理的目的就让它能够同时支持旋转跟缩放
rotation.delegate = self;
也要做复位操作
rotation.rotation = 0;
}
3.添加缩放手势
添加缩放手势
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
[self.imageV addGestureRecognizer:pinch];
缩放手势时调用
-(void)pinch:(UIPinchGestureRecognizer *)pinch{
平移也是相对于上一次
self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pinch.scale,
pinch.scale);
复位
pinch.scale = 1;
}