iOS开发之UI篇(4)—— 触摸事件

来源:互联网 发布:adobe flash cs6 mac 编辑:程序博客网 时间:2024/06/13 05:16

版本

Xcode 9.1

UIApplication、UIViewController、UIView均继承自UIResponder。
在iOS中,只有继承了UIResponder的对象才能接收处理事件,此对象即为事件“响应者”。
接下来从响应方法、触摸对象UITouch、事件对象UIEvent和响应链这四个方面进一步分析。然后写个示例,也就算了。

1. 响应方法

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;/** 当无法获得touches真实值的时候调用该方法(iOS9.1开始引入) 当UIKit无法得到准确的touches值的时候(例如触摸笔处在屏幕边缘,此时无法获得触摸笔的高度和定位), 会生成此touches的预估值,导致estimatedPropertiesExpectingUpdates这个属性发生改变, 当属性estimatedPropertiesExpectingUpdates发生改变(不为空或者更新)时,调用该方法 */- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

2. 触摸对象UITouch

当手指触摸屏幕时,会创建一个与手指相关的UITouch对象,且一根手指对应一个对象。
如果多根手指同时触摸屏幕,那么会依次创建多个对象,这些对象会被包含在响应方法的形参touches里面。
UITouch中包含的常用信息有:

window属性:触摸时所在的窗口
view属性:触摸时所在视图
tapCount属性:短时间内点击的次数(可判断单点还是多点)
timestamp属性:触摸产生或变化的时间戳
phase属性:触摸周期内的各个状态
locationInView:方法:取得在指定视图的位置
previousLocationInView:方法:取得移动的前一个位置

3. 事件对象UIEvent

当发生一个事件时,就会对应产生一个UIEvent对象。
在一次完整的触摸过程中,只会产生一个UIEvent,也就是说,五个响应方法都是同一个UIEvent对象(即响应方法的形参event)。
UIEvent中包含的常用信息有:

type属性:事件类型
timestamp属性:事件产生的时间
touchesForWindow:方法:返回当前窗口上的触摸对象UITouch
touchesForView:方法:返回特定视图上的触摸对象UITouch

4. 响应链

前面讲到UIApplication、UIViewController、UIView均能响应触摸事件,当有多个view重叠的时候触摸事件是怎样响应的呢?
图解:


触摸事件响应链
触摸事件响应链

  1. 当一个事件发生后首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView);
  2. 如果上级视图(黄色view)仍然无法处理则会继续往上传递;
  3. 一直传递到视图控制器view controller,首先判断视图控制器的根视图(蓝色view)是否能处理此事件;
  4. 如果不能则接着判断该视图控制器view controller能否处理此事件,如果还是不能则继续向上传递;
  5. 一直到window,如果window还是不能处理此事件则继续交给application(UIApplication单例对象)处理;
  6. 如果最后application还是不能处理此事件则将其丢弃。

在这个过程中各个对象如何知道自己能不能处理该事件呢?对于继承UIResponder的对象,其不能处理事件有几个条件:

  1. userInteractionEnabled = NO(其子视图也将无法响应事件)
  2. hidden = YES
  3. alpha = 0~0.01
  4. 没有实现开始触摸方法(touchesBegan:withEvent:)

注:前三点都是针对UIView控件或其子控件而言的

示例

在ViewController的子视图(self.view)上面依次添加三个view:GreenView 、BlueView 和RedView。ViewController和这三个view均实现触摸方法,其中在三个view的触摸移动方法中均设置该view中心点跟随触摸点移动(这样我们触摸移动哪个view时,这个view就会跟随触摸点移动),在ViewController的触摸移动方法中均设置背景颜色随机变化。下面结合效果图来解析:

  1. 触摸RedView时,由于RedView中设置了userInteractionEnabled = No,使得RedView并没有响应事件(相对于父视图移动),而是将事件传递到父视图(BlueView),由父视图响应事件;
  2. 触摸BlueView 时,BlueView响应了触摸事件(相对于父视图移动),没有将事件传递给它的父视图(GreenView),所以拖动BlueView时,GreenView并没有动;
  3. 触摸GreenView时,GreenView响应了触摸事件(相对于父视图移动),由于在GreenView的touchesBegan方法里将事件传递给下一个响应者,使得ViewController也响应了事件(背景颜色随机改变)。

    效果图
    效果图

RedView.m

- (void)drawRect:(CGRect)rect {    self.userInteractionEnabled = NO;//    self.hidden = YES;//    self.alpha = 0.5;}#pragma mark - 触摸事件响应方法// 触摸开始- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"RedView:touchesBegan");}// 触摸移动中(随着手指的移动,会多次调用该方法)- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"RedView:touchesMoved");    /* 设置当前view随手指移动 */    // 取得一个触摸对象(对于多点触摸可能有多个对象)    UITouch *touch = [touches anyObject];    // 取得触摸点在当前视图中的位置    CGPoint current = [touch locationInView:self];    // 取得前一个触摸点位置    CGPoint previous = [touch previousLocationInView:self];    // 触摸点移动偏移量    CGPoint offset = CGPointMake(current.x-previous.x, current.y-previous.y);    // 当前视图移动前的中点位置(相对于父视图)    CGPoint center = self.center;    // 重新设置当前视图新位置    self.center = CGPointMake(center.x+offset.x, center.y+offset.y);}// 触摸结束- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"RedView:touchesEnded");}// 触摸被取消(触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程)- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"RedView:touchesCancelled");}// 当无法获得touches真实值的时候调用该方法(一般使用触摸笔才出现此状况)- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches {    NSLog(@"RedView:touchesEstimatedPropertiesUpdated");}

BlueView.m

#pragma mark - 触摸事件响应方法// 触摸开始- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"BlueView:touchesBegan");}// 触摸移动中(随着手指的移动,会多次调用该方法)- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"BlueView:touchesMoved");    /* 设置当前view随手指移动 */    // 取得一个触摸对象(对于多点触摸可能有多个对象)    UITouch *touch = [touches anyObject];    // 取得触摸点在当前视图中的位置    CGPoint current = [touch locationInView:self];    // 取得前一个触摸点位置    CGPoint previous = [touch previousLocationInView:self];    // 触摸点移动偏移量    CGPoint offset = CGPointMake(current.x-previous.x, current.y-previous.y);    // 当前视图移动前的中点位置(相对于父视图)    CGPoint center = self.center;    // 重新设置当前视图新位置    self.center = CGPointMake(center.x+offset.x, center.y+offset.y);}// 触摸结束- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"BlueView:touchesEnded");}// 触摸被取消(触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程)- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"BlueView:touchesCancelled");}// 当无法获得touches真实值的时候调用该方法(一般使用触摸笔才出现此状况)- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches {    NSLog(@"BlueView:touchesEstimatedPropertiesUpdated");}

GreenView.m

#pragma mark - 触摸事件响应方法// 触摸开始- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"GreenView:touchesBegan");    // 传递触摸事件给下一响应者    [super touchesBegan:touches withEvent:event];    // 用[self nextResponder]传递过去的事件只响应touchesBegan,而不响应touchesMoved、touchesEnded等//    [[self nextResponder] touchesBegan:touches withEvent:event];}// 触摸移动中(随着手指的移动,会多次调用该方法)- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"GreenView:touchesMoved");    /* 设置当前view随手指移动 */    // 取得一个触摸对象(对于多点触摸可能有多个对象)    UITouch *touch = [touches anyObject];    // 取得触摸点在当前视图中的位置    CGPoint current = [touch locationInView:self];    // 取得前一个触摸点位置    CGPoint previous = [touch previousLocationInView:self];    // 触摸点移动偏移量    CGPoint offset = CGPointMake(current.x-previous.x, current.y-previous.y);    // 当前视图移动前的中点位置(相对于父视图)    CGPoint center = self.center;    // 重新设置当前视图新位置    self.center = CGPointMake(center.x+offset.x, center.y+offset.y);}// 触摸结束- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"GreenView:touchesEnded");}// 触摸被取消(触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程)- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"GreenView:touchesCancelled");}// 当无法获得touches真实值的时候调用该方法(一般使用触摸笔才出现此状况)- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches {    NSLog(@"GreenView:touchesEstimatedPropertiesUpdated");}

ViewController.m

#pragma mark - 触摸事件响应方法// 触摸开始- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"ViewController:touchesBegan");}// 触摸移动中(随着手指的移动,会多次调用该方法)- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"ViewController:touchesMoved");    // 设置背景随机颜色    CGFloat red = (CGFloat)random() / (CGFloat)RAND_MAX;    CGFloat green = (CGFloat)random() / (CGFloat)RAND_MAX;    CGFloat blue = (CGFloat)random() / (CGFloat)RAND_MAX;    self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];}// 触摸结束- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"ViewController:touchesEnded");}// 触摸被取消(触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程)- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"ViewController:touchesCancelled");}/** 当无法获得touches真实值的时候调用该方法(一般使用触摸笔才出现此状况) 当UIKit无法得到准确的touches值的时候(例如触摸笔处在屏幕边缘,此时无法获得触摸笔的高度和定位), 会生成此touches的预估值,导致estimatedPropertiesExpectingUpdates这个属性发生改变, 当属性estimatedPropertiesExpectingUpdates发生改变(不为空或者更新)时,调用该方法 @param touches 触摸对象 */- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches {    NSLog(@"ViewController:touchesEstimatedPropertiesUpdated");}