1 IOS事件

IOS中的事件有3大类型:

· 触摸类型

· 加速加速计事件

· 远程控制事件

本文只讨论IOS的触摸事件

1.1响应者对象(UIResponder)

在IOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接受并处理事件,我们称之为“响应者对象”。

UIApplication、UIViewController、UIView都继承自UIResponder,均能接受并处理事件。

那UIResponder为什么能接受并处理事件呢?因为UIResponder提供了4个对象方法来处理触摸事件。

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;

2 事件的处理

下面以UIView为例来说明触摸事件的处理。

// UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件

// 一根或者多根手指开始触摸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

// 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

提示:touches中存放的都是UITouch对象。

上面四个方法都是系统自动调用的,所以我们可以通过重写该方法来处理一些事件。

· 如果两根手指同时触摸一个view,那么view只会调用一次touchBegin:withEvent:方法,touches参数中装着两个UITouch对象

· 如果两根手指一前一后分开触摸同一个View,那么view会调用两次touchBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象

· 重写以上四个方法,如果是处理UIView的触摸事件,必须要自定义UIView子类继承自UIView。因为苹果不开源,我们无法修改UIView.m文件,只能通过子类继承父类,重写父类方法来处理UIView的触摸事件()

· 上面我们说的是UIVIew的触摸事件,但是如果要处理UIViewController的触摸事件,那么直接在控制器的.m文件中重写四个方法即可

2.1 UIView的拖拽

我们见过很多场景,可以将一个对象拖来拖去,那怎样实现的呢?即让UIView随着手指的移动而 移动。

那就是重写touchMoved:withEvent:方法

此时需要用到参数touches。

2.1.1 UITouch对象

· 当用户用一根手指触摸屏幕的时候,会创建一个与手指相关的UITouch的对象。

· 一根手指对应一个UITouch对象

· 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着两个UITouch对象

· 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用两次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象

2.1.1.1 UITouch的作用

· 保持着根手指相关的信息,比如触摸的位置、事件和阶段

· 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指所在的触摸位置

· 当手指离开屏幕时,系统会销毁相应的UITouch对象

iPhone开发中,避免双击事件

UITouch属性

触摸产生时所处的窗口

@property(nonatomic,readonly,retain) UIWindow *window;

触摸产生时所处的视图

@property(nonatomic,readonly,retain) UIView *view;

短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击

@property(nonatomic,readonly) NSUInteger tapCount;

记录了触摸事件产生或变化时的时间,单位是秒

@property(nonatomic,readonly) NSTimeInterval timestamp;

当前触摸事件所处的状态

@property(nonatomic,readonly) UITouchPhase phase;

2.1.1.2 UITouch方法

(CGPoint)locationInView:(UIView *)view;

// 返回值表示触摸在view上的位置

// 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))

// 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置

(CGPoint)previousLocationInView:(UIView *)view;

// 该方法记录了前一个触摸点的位置

代码实现

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{

// 想让控件随着手指移动而移动,监听手指移动

// 获取UITouch对象

UITouch *touch = [touches anyObject];

// 获取当前点的位置

CGPoint curP = [touch locationInView:self];

// 获取上一个点的位置

CGPoint preP = [touch previousLocationInView:self];

// 获取它们x轴的偏移量,每次都是相对上一次

CGFloat offsetX = curP.x - preP.x;

// 获取y轴的偏移量

CGFloat offsetY = curP.y - preP.y;

// 修改控件的形变或者frame,center,就可以控制控件的位置

// 形变也是相对上一次形变(平移)

// CGAffineTransformMakeTranslation:会把之前形变给清空,重新开始设置形变参数

// make:相对于最原始的位置形变

// CGAffineTransform t:相对这个t的形变的基础上再去形变

// 如果相对哪个形变再次形变,就传入它的形变

self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);}

3 IOS中事件的产生和传递

3.1 事件的产生

· 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中(队列中FIFO,先产生的事件下执行)

· UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keywindow)。

· 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件的处理过程的第一步,找到合适的视图后,就会调用视图空间的touches方法来作为具体的事件处理。

事件的传递

· 触摸事件的传递是从父空间传递到子控件

UIApplication -> window -> 寻找处理时间的最合适的view

3.1.1 找到最合适的控件来处理事件

首先判断主窗口能否接受触摸事件

判断触摸点是否在自己身上

子控件数组中从后往前(子控件数组中最后一个元素开始)遍历子控件,重复前面的两个步骤(从后往前是为了让最上层的响应者最先响应时间)

找到一个view,将事件交给这个view,然后再遍历这个view的子控件,直到找到没有更合适的控件

如果没有符合条件的子控件,那就认为自己最合适处理这个时间,也就是自己最合适的view

UIView不能接受触摸事件的三种情况:

不允许交互---userInteractionEnable = NO;

隐藏---如果父控件隐藏,子控件也会隐藏,隐藏空间不能接受事件

透明度---如果设置一个空间的透明度 <= 0.01,会直接影响子控件的透明度。当透明度<= 0.01的时候,认为是透明

UIImageView默认不接受触摸事件,不允许交互,即userInteractionEnable = NO;若希望UIImageView可以交互,需要显式设置UIImageView的userInteractionEnable = YES;

3.2 顺一下流程

1.点击一个UIView或产生一个触摸事件A,这个触摸事件A会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。

2.UIApplication会从事件对列中取出最前面的事件(此处假设为触摸事件A),把事件A传递给应用程序的主窗口(keyWindow)。

3.窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)

5900e38d86a8

如果想让某个view不能处理事件(或者说,事件传递到某个view那里就断了),那么可以通过刚才提到的三种方式。比如,设置其userInteractionEnabled = NO;那么传递下来的事件就会由该view的父控件处理。

例如,不想让蓝色的view接收事件,那么可以设置蓝色的view的userInteractionEnabled = NO;那么点击黄色的view或者蓝色的view所产生的事件,最终会由橙色的view处理,橙色的view就会成为最合适的view。

所以,不管视图能不能处理事件,只要点击了视图就都会产生事件,关键在于该事件最终是由谁来处理!也就是说,如果蓝色视图不能处理事件,点击蓝色视图产生的触摸事件不会由被点击的视图(蓝色视图)处理!

注意:如果设置父控件的透明度或者hidden,会直接影响到子控件的透明度和hidden。如果父控件的透明度为0或者hidden = YES,那么子控件也是不可见的!

3.3如何找到最适合的view

3.3.1

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐