IOS事件处理

来源:互联网 发布:方舟子反对中医 知乎 编辑:程序博客网 时间:2024/06/05 15:49



http://wenku.baidu.com/view/717d317f168884868762d66f.html





IOS事件处理

概述

事件

       一个事件即由硬件捕捉并产生的一个表示用户操作设备的对象并发送给IOS——例如:一个手指点击屏幕或摇动设备。许多event都是UIKit框架中UIEvent类的实例。UIEvent对象可能封装了用户事件所关联的状态,     像关联的点击。UIEvent对象同时也记录了事件所产生的时刻。当一个用户事件发生时——例如,当手指点击屏幕并在其上移动——IOS连续发送事件对象给应用程序进行处理。

       UIKit当前可识别三种类型的事件:点击事件,摇动事件及远程控制事件。UIEvent类对其定义了enum常量:

typedef enum {

       UIEventTypeTouches,

       UIEventTypeMotion,

       UIEventTypeRemoteControl

} UIEventType;

 

typedef enum {

       UIEventSubtypeNone = 0,

       UIEventSubtypeMotionShake = 1,

       UIEventSubtypeRemoteControlPlay = 100,

       UIEventSubtypeRemoteControlPause = 101,

       UIEventSubtypeRemoteControlStop = 102,

       UIEventSubtypeRemoteControlTogglePlayPause= 103,

       UIEventSubtypeRemoteControlNextTrack =104,

       UIEventSubtypeRemoteControlPreviousTrack= 105,

       UIEventSubtypeRemoteControlBeginSeekingBackward= 106,

       UIEventSubtypeRemoteControlEndSeekingBackward= 107,

       UIEventSubtypeRemoteControlBeginSeekingForward= 108,

       UIEventSubtypeRemoteControlEndSeekingForward= 109

} UIEventSubtype;

       每个事件都有其对应的事件类型和子事件类型,它们可以由UIEvent对象的type和subtype属性进行访问。事件类型包括点击事件,摇动事件及远程控制事件。在IOS3.0中,包括了摇动子事件类型(UIEventSubtypeMotionShake)及多种远程控制子事件类型;点击事件所对应的子事件类型为UIEventSubtypeNone。

       你不应该在你代码中retain一个UIEvent对象。如果你需要保留一个事件对象的当前状态为以后进行处理,你应该通过合适的方法(使用实例变量或字典对象)拷贝或保存状态值。

 

 

事件传递

       从事件发生到其处理的对象,传递要经过相当长及特殊的一段过程。当用户点击设备屏幕时,IOS捕捉到一系列的触摸,将其打包到UIEvent对象中并放置到应用程序活动事件队列中。如果系统检测到摇动设置为一个有效的移动事件,则也将其打包并放置到应用程序活动事件队列中。单例UIApplication对象管理应用程序,从事件队列中取出最前面的事件并将其分发以便处理。通常,其发送事件给应用程序的主窗口——即聚焦于用户交互的窗口——窗口对象代表窗口再发送事件给初始对象进行处理。初始对象对于点击事件和摇动事件是不相同的。

       点击事件。窗口对象使用hit-testing及响应者链表去找到对该事件响应的视图。在hit-testing中,窗口找到视图继承树中最上层的视图并调用hitTest:withEvent:方法;该方法递归调用视图继承树中每个视图的pointInSide:withEvent:方法并在其返回true后,从该视图开始沿着继承树向下,直到找到一个点击事件在其范围之内的子视图。该子视图即为hit-test视图。

       如果该hit-test视图无法处理该事件,则事件沿着响应者链表进行传递直至找到能处理该事件的视图。一个点击对象在其生命周期中一直与hit-test视图相关联,即使点击对象移动到一样该视图之外(?)。

       摇动及远程控制事件。窗口对象发送每个摇动及远程控制事件给首响应者进行处理。

      

       尽管hit-test视图和首响应者通常是同一个视图对象,但它们并不总是相同。

      

       UIApplication对象及每个UIWindow对象在sendEvent:方法中进行事件传递,因为这些方法是从应用程序传递来事件的汇集点,所以你可以继承UIApplication或UIWindow并重载sendEvent:方法来监控事件(只有少量的应用程序需要此实现)。如果你重载了该方法,请确保调用了超类的sendEvent:方法。无论如何都不要篡改事件的分发。

 

响应对象和响应者链

       前面的讨论已涉及到响应者的概念。那什么是响应者及它是如何适应消息分发的框架呢?

       响应者对象是一个能接收并处理事件的对象。UIResponser是所有响应者对象对的基类,另一个熟悉并简单的名字为响应者。该基类定义了一系列编程接口,不但为事件处理进行服务而且还提供了通用的响应行为处理。UIApplication,UIView及所有作为UIView子类的UIKit类(包括UIWindow)都直接及间接的继承自UIResponser,所有的这些类的实例都是响应者对象。

       首响应者是应用程序的一个响应者对象(通常为UIView)作为接收除点击事件以外其它事件的首个响应对象。UIWindow对象通过消息发送摇动或远程控制事件给首响应者,让其对事件进行优先处理。为了能接收到UIWindow对象发送来的消息,响应者对象必需申明canBecomeFirstResponser方法并返回Yes;并能接收到becomeFirstResponser消息(该方法可以在对象内部进行调用)。首响应者是Window中的第一个视图用以接收以下的事件或消息:

       摇动事件——通过调用UIResponser的摇动处理方法

       远程控制事件——通过调用UIResponser的remoteControlReceivedWithEvent方法

       操作消息——当用户操作了Control(像按钮或滑块)便没有指定消息的接收者

       Editing-Menu Message

       首响应者也在文字编辑中扮演作用。当对TextView或Text Field聚焦进行输入时即成为了首响应者,并促使了虚拟键盘的出现。

       注:应用程序必需显式的为摇动,远程控制事件及editing-menu消息处理设置首响应者,当用户点击Text View或Text Field时,UIKit自动将其设置为首响应者。

       如果首响应者或hit-test视图不处理收到的消息,UIkit则通过消息的形式将事件转发到响应者链的下一个响应者,看其是否能过该消息进行处理。

       响应者链是一系列已连接的响应者对象,事件或消息在其路径上进行传递。在其上允许响应者将事件处理的责任传递给高层的其它响应者。应用程序根据响应者的处理能力将事件向上进行传递。因为hit-test视图是一个响应者对象,当应用程序接收到点击事件时可能会对其进行更进一步的处理。响应者链包括一系列的下一个响应者(每一个通过nextResponser方法返回)。如下图所示。

      


       当系统转发了一个点击事件,首先将其发送给特殊的视图。对于点击事件,这个特殊视图为调用hitTest:withEvent方法所返回的视图;对于它事件或消息,该特殊视图即为首响应者。如果首个视图无法处理收到的事件,则其通过在响应者链中的特殊路径向上传递。

1.      hit-test视图或首响应者发送事件或消息给其对应的视图控制器,如果该控制器存在;如果控制器不存在,则将其传递给它的父视图

2.      如果视图或它的控制器无法处理收到的事件或消息,则将其传递给该视图的父视图

3.      每一个在视图继承树中的上层视图如果不能处理收到的事件或消息,则重复上面的步骤1,2

4.      在视图继承树的最上层视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给窗口对象进行处理

5.      如果窗口对象也不能进行处理,则其将事件或消息传递给UIApplication对象

6.      如果UIApplication也不能处理该事件或消息,则将其丢弃

       如果你有自定义的处理摇动,远程控制事件或其它消息的视图,你不应该将事件或消息直接发送到响应者链的下一个响应者。取代方法是调用父类的当前事件处理方法,让UIKit对消息传递进行处理。

多点触摸事件

       触摸事件在IOS中是基于多点触摸模型。取代鼠标和键盘,用户通过触摸设备的屏幕来操作对象,输入数据以及实现其它意愿。IOS识别的一个或多个手指对屏幕进行触摸并将其作为多点触摸序列的一部分。该序列以用户第一个手指触摸屏幕开始一直持续到最后一个手指离开屏幕。IOS通过触摸序列来跟踪手指在屏幕上的移动并记录它们的特征,包括手指在屏幕上的位置以及触摸发生的时间。应用程序通常会识别一组触摸为一个手势并对其进行相应的响应,例如:放大缩小屏幕内容用于响应pinch手势,对屏幕内容进行滚动用于响应flick手势。

       UIKit中许多类对于多点触摸的处理有别于其对象。对于UIControl类的子类则特别如此,像UIButton和UISlider。对于从UIControl继承的对象通常称为控制对象,它们期望收到特定的手势,像点击或向某方向进行拖动。通过适应的配置,当手势发生时它们会发送操作消息给目标对象。其它的UIkit类以不同的方式对手势进行响应,以ScrollView为例,为表视图,文本视图以及其它有大块内容区域视图提供滚动行为。

       有些应用程序不需要直接响应事件,它们信赖于UIkit类所提供的行为。如果你创建了一个UIView的子类并且希望该视图对触摸进行响应,你就必须为处理触摸事件而编写代码。如果你希望UIKit中的对象对于事件有不同的响应,你就必须对UIkit中使用到的该类进行继承并重载合适的事件处理方法。

事件和触摸

       在IOS中,触摸表示手指在屏幕上的接触或移动并作为唯一多点触摸序列的一部分。例如:挤(pinch-close)手势有两个触摸:两个手指在屏幕上以相反的方向向对方移动。同时也有一个手指操作的简单手势,例如:单击,双击,拖动和滑动(flick)。应用程序也有可能处理复杂的手势。

       一个具有UIEventTypeTouches类型的UIEvent对象表示一个触摸事件。当手指触摸屏幕并在其上移动时,系统持续的发送这些触摸事件对象给应用程序。在触摸序列中,事件为所有触摸提供了快照,最重要的触摸为新收到的或在特定视图中改变的。多触摸序列从第一个手指触摸到屏幕开始。其它的手指可能接下来再触摸屏幕并且所有在屏幕上的手指进行移动,序列当最后一个手指离开屏幕时结束。

      


       触摸,用UITouch对象来表示,包括触摸阶段和触摸位置。触摸阶段,指示什么时候触摸开始,是否移动和什么时候触摸结束——即何时最后一根手指离开屏幕。

       触摸位置表示与触摸相关联的对象即触摸点在对象的内部。当手指触摸到屏幕,触摸就与窗口和视图相关联,关联关系一直维持到事件结束。如果多点触摸同时到达,当它们所关联的视图相同时则进行统一处理。否则,如果两个触摸在同一个视图上以很快的速度先后到达,则被认为是多次点击。触摸对象保存触摸的位置及上次触摸位置(如果存在)在它所对应的窗口或视图中。

       一个事件对象包括了在本次触摸序列中所有的触摸对象并能提供触摸对象所操作的窗口或视图(如图2-2)。所改变的触摸属性为触摸阶段,在视图中的位置,上一次的位置以及触摸时间戳。事件处理代码计算这些属性以决定如何对其进行响应。

      


       因为系统可以在任何时候取消多点触摸序列,所以应用应能对其进行相应的处理。取消会发生在有系统消息到达的时候,例如一个来电或短消息。

事件处理方法

       大多数应用程序可以在自定义视图上检测并处理其感兴趣的手势。这些手势包括点击(单击或多击),pinch(放大或缩小视图),滑动,拖动一个视图或用两个手指来旋转一个视图。

       你可以定义触摸事件处理代码来识别并处理这些手势,但这样的代码将十分的复杂,可能会有很多bug且会耗费大量时间。替代方法是在IOS3.2以后,你可以使用手势识别器类来定义和处理这些通用的手势。要使用手势识别器类,首先进行初始化,将其与要接收触摸的视图联系,对其进行配置,并分配该手势的处理对象及处理方法。当一个手势识别器识别到一个手势时,它将发送处理方法给处理对象来对手势进行响应。

       你可以通过继承UIGestureRecognizer类来定义自己的手势识别器类。自定义的手势识别器需要你对一个多触摸序列中的触摸流进行分析来识别特定的手势。

控制触摸事件投递

       UIKit提供给应用程序事件处理编程接口或可以完全关闭UIEvent对象流,下面的则描述了操作方法。

       关闭事件投递。缺省情况下,视图接收触摸事件,但你可以设置视图的userInteractionEnable属性为NO来关闭对触摸事件的投递,一个视图在隐藏或透明状态下也无法接收到触摸事件。

       在一段时间内关闭事件投递。应用程序可以调用UIApplication的beginIgnoringInteraction

Events方法并在以后的某个时间调用endIgnoringInteractionEvents方法。第一方法使application对象完成停止对触摸事件的接收,第二个方法则是恢复application对象对触摸事件的接收。你可能在应用中运行动画时关闭对事件的投递。

       打开多触摸事件投递。缺省情况下,视图只会对触摸序列中的第一个触摸进行响应并忽略其它触摸。如果你需要处理多触摸则必需打开该视图的多触摸处理能力。你可以通过编程设置multipleTouchEnable属性为YES或者通过Interface Builder中设置勾选相关属性。

       限制触摸事件给特定视图。缺省情况下,视图的exclusiveTouch属性设置为NO,表示该视图并不限制当前窗口中的其它视图接收触摸事件。如果你设置其值为YES,则标识了该视图并只有该视图能对消息进行跟踪处理。窗口中的其它视图则无法收到触摸事件。

       限制触摸事件给子视图。一个从UIView继承的自定义视图,可以重载hitTest:withEvent方法来限制将多触摸事件传递给其子视图。

处理多触摸事件

       要处理多触摸事件,你首先必须创建一个响应者的子类。该类可以从以下的任一个进行继承:

       一个自定义视图(UIView的子类)

       一个UIViewController类的子类或从UIKit中其子控制器类继承

       一个UIkit中View或控制类的子类,像UIImageView或UISlider

       一个UIApplication或UIWindow的子类(不常用)

       一个视图控制器一般通过响应者链来接收事件,触摸事件一开始便发送给它对应的视图如果该视图没有重载事件处理方法。

       如果要自定义的子类实例能接收触摸事件,你必须为触摸事件的处理申明一个或多个UIResponser方法,方法描述见下。除此之外,对应的视图应可见(不是透明或隐藏)并且userInteractionEnabled应为YES(缺省值)。

       接下来的章节描述事件处理方法,描述处理通用手势的方法,展示了一个用于处理复杂多触摸事件的响应者例子,讨论事件转发和事件处理所建议使用的技术。

事件处理方法

       在一个多触摸序列中,应用程序分发一系列的事件消息给目的响应者。为了能接收并响应这些消息,响应者类必须至少申明一个由UIResponser类定义的如下方法,在一些情况下,则必须申明全部的方法。

-    (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

       应用程序在一个给定的触摸阶段当有新的触摸或改变的触摸时发送这些消息:

·     当一根或多根手指触摸到屏幕时发送touchesBegan:withEvent:消息。

·     当一根或多根手指在屏幕上移动时发送touchesMoved:withEvent:消息。

·     当一根或多根手指离开屏幕时发送touchesEnded:withEvent:消息。

·     当触摸序列被一个系统事件(来电)打断时发送touchesCancelled:withEvent:消息。

       上述的每一个消息都与一个触摸阶段相关联,例如:touchesBegan:withEvent:消息与UITouchPhaseBegin相关联。你可以通过访问任何UITouch对象的phase属性获得该触摸所处的阶段。

       每个调用事件处理方法的消息都包括两个参数。第一个参数是一组表示在给定触摸阶段新收到或改变了的UITouch对象。第二个参数则是表示触摸事件的UIEvent对象。从事件对象中你可能获得该事件的所有触摸对象或者是根据给定视图或窗口过滤了的触摸对象。其中的一些触摸对象表示该触摸自从上个事件来没有发生改变或该触摸在不同的触摸阶段发生了改变。

基本触摸事件处理

       你通常会处理一个给定阶段的事件通过从传入的一系列UITouch中获得一个或多个UITouch对象,计算属性,获得位置并进行相应的处理。这些对象表示申明的处理方法中在一个给定阶段新收到或改变了的触摸。如果收到了任一个触摸对象,你可以向NSSet对象发送anyObject消息,该消息导致对应视图只接收触摸序列中的第一个触摸(multipleTouchesEnabled属性设置为NO)。

       UITouch对象的一个重要方法为locationInView:,如果为该方法传递self参数,则表示该触摸的位置位于接收视图中的坐标。一个并行的方法告诉你该触摸上一次的位置(previousLocationInView:)。UITouch对象的tapCount属性告诉你本次触摸点击了多少次,timestamp属性告诉你本次触摸何时创建以及最后一次改变的时间,phase属性则告诉你本次触摸所处的阶段。

       由于某些原因,你可能对本次触摸序列中那些自从上个阶段以来没有改变或处在不同阶段的同一触摸感兴趣,你可以从传入的UIEvent对象中获得它们。下图中展示了一个UIEvent对象中包括四个触摸对象。要获得所有的触摸对象,你可以调用事件对象的allTouches方法。


      

       如果你只对在给定窗口中发生的触摸感兴趣,你将给UIEvent对象发送touchesForWindow:消息,如下图所示。

      


       如果你只对在给定视图中发生的触摸感兴趣,你将给UIEvent对象发送touchesForView:消息,如下图所示。

      


       如果一个响应者在对事件序列中的事件进行处理时创建了持久化对象,则应该申明touchCancelled:withEvent:方法,并在该方法中对持久化对象进行释放。事件取消通常发生在外部事件——例如来电——打断了当前应用程序的事件处理。需要注意的是响应者对象也应该释放在touchEnded:withEvent:方法中进行释放的对象。

       重要:如果自定义的响应者是UIView或UIViewController的子类,则你必须申明全部四个UIResponser对象的事件处理方法。如果自定义响应者为UIKit中的响应者类,则你不需要对全部四个事件处理方法进行申明。在那些重载的事件处理方法中,一定要调用其超类的事件处理方法([super touchBegan:toucheswithEvent:theEvent])。这样做的原因很简单:所有的视图都处理触摸,包括你自定义的,都希望收到完整的触摸事件流。如果你阻止一个UIKit响应者对象接收在给定阶段中的触摸事件,则接下来的行为将是未定义并不可预计。

点击手势处理

       点击是在IOS应用中最常用的手势:用户通过手指点击屏幕上的对象。响应者对象可以对单击,双击以及三击进行不同的响应。要确定用户点击的次数,你可以调用UITouch对象的tapCount属性获得。

       获得点击次数的最佳位置是在touchBegan:withEvent:和touchEnded:withEvent:方法中。而第二个方法则更为合适,因为它在用户手指从点击到离开屏幕的阶段中进行响应。通过在touch-up阶段计算点击次数,你可以确认手指是否真正的完成点击,或者只是手指按到屏幕上并进行移动。

       下面的代码展示了如何在指定视图中对双击进行判断。

              (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent*)event { }

              (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event { }

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

              (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent *)event {

                   for(UITouch *touch in touches) {

                            if (touch.tapCount >= 2) {

                                     [self.superviewbringSubviewToFront:self];

                             }

                      }

                   }

       如果响应者对象将对单击和双击操作进行不同的处理则会变得有点复杂。例如:单击操作为了选中对象而双击操作为了显示一个视图并对其进行编辑。应用程序应该如何判断单击并非是双击的第一部分呢?下面的代码示例了对消息处理方法的声明让进行双击操作时增加所点击视图的大小而单击操作时减少所点击视图的大小。

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

    UITouch *touch= [touches anyObject];

    if (2 ==touch.tapCount) {

        [NSObject cancelPreviousPerformRequestsWithTarget:self];

    }

}

 

- (void)touchesMoved:(NSSet *)toucheswithEvent:(UIEvent *)event { }

 

- (void)touchesEnded:(NSSet *)toucheswithEvent:(UIEvent *)event {

    UITouch *touch= [touches anyObject];

    if (1 ==touch.tapCount) {

        NSDictionary *touchLoc = [NSDictionarydictionaryWithObject:

                                        [NSValuevalueWithCGPoint:[touch locationInView:self]]

                                                            forKey:@"location"];

        [self performSelector:@selector(handleSingleTap:)withObject:touchLocafterDelay:0.3];

    }

    else if (2 ==touch.tapCount) {

        //double-tap: increase view size by 10%

        CGRectviewFrame = self.frame;

       viewFrame.size.width +=self.frame.size.width *0.1;

       viewFrame.size.height +=self.frame.size.height *0.1;

       viewFrame.origin.x -= (self.frame.origin.x * 0.1)/2.0;

       viewFrame.origin.y -= (self.frame.origin.y * 0.1)/2.0;

       

        [UIView beginAnimations:nil context:NULL];

        [self setFrame:viewFrame];

        [UIView commitAnimations];

    }

}

 

- (void)touchesCancelled:(NSSet *)toucheswithEvent:(UIEvent *)event { }

 

- (void)handleSingleTap:(NSDictionary*)touches {

    //Single-tap: decrease view size by 10%

    CGRectviewFrame = self.frame;

    viewFrame.size.width -=self.frame.size.width *0.1;

    viewFrame.size.height -=self.frame.size.height *0.1;

    viewFrame.origin.x += (self.frame.origin.x * 0.1)/2.0;

    viewFrame.origin.y += (self.frame.origin.y * 0.1)/2.0;

   

    [UIView beginAnimations:nilcontext:NULL];

    [self setFrame:viewFrame];

    [UIView commitAnimations];

}

       以下是对上面代码的说明:

       在touchEnded:withEvent:方法中,当点击的次数为1时,响应者对象向其自身发送performSelector:withObject:afterDelay:消息。选择子标识了对单击事件响应的方法;第二个参数是一个NSValue或NSDictionary对象,用来保存UITouch对象的一些状态;延迟时间是表示单击和双击之间有意义的时间间隔。

       注:因为touch对象在触摸序列的处理中是可变的,所以你不能retain一个touch对象并假设它的状态不会发生改变(你也不能拷贝一个touch对象,因为其没有申明NSCopying协议)。但如果你需要保存touch对象的某些状态,你需要将touch对象的状态用NSVaule对象,字典对象或其它类似对象进行保存。

       在touchBegan:withEvent:方法中,当点击的次数为2时,响应者对象通过调用NSObject类的cancelPerformRequestsWithTarget:方法取消被挂起的延迟调用。如果点击次数不为2,则在延迟时间到达后,将会调用选择子所指定的单击事件处理方法。

       在touchEnded:withEvent:方法中,当点击的次数为2时,响应者对双击的操作进行处理。

处理滑动或拖动手势

       水平或垂直方面的滑动属于简单的手势,你可以在你的代码中进行跟踪并对其进行处理。要检测一个滑动手势,你必须跟踪与所希望的移动轴相反的用户手指的移动,但是滑动的组成最终还是由你来决定。换句话说,你需要确定用户手指是否移动的足够远,是否以直线方式向前移动,是否移动的足够快。要对上述的内容进行判断,你要存储下初始时的触摸位置,然后与touch-moved事件中位置进行比较。 

       下面的代码示例了一些基本的跟踪方法用于判断视图上进行水平方向的滑动。在这个例子中首先将触摸的初始位置存储到startTouchPosition实例变量中。当用户移动手指,代码对当前的触摸位置与初始触摸位置进行比较来确定是否为滑动操作。如果手指在垂直方向上移动了太多的距离,则不认为是一次滑动而进行不同的处理。一旦滑动足够远并被认为是一个完整的手势时会调用相应的操作。要在垂直方向上判断滑动操作,你也可以使用类似的代码,只不过对对X和Y方向进行交换。

#defineHORIZ_SWIPE_DRAG_MIN    12

#define VERT_SWIPE_DRAG_MIN     4

 

#pragma mark event handle

- (void)touchesBegan:(NSSet *)toucheswithEvent:(UIEvent *)event {

    UITouch *touch= [touches anyObject];

   

    startTouchPosition = [touch locationInView:self];

}

 

- (void)touchesMoved:(NSSet *)toucheswithEvent:(UIEvent *)event {

    UITouch *touch= [touches anyObject];

    CGPointcurrentTouchPosition = [touch locationInView:self];

   

    if ((fabs(startTouchPosition.x - currentTouchPosition.x) >=HORIZ_SWIPE_DRAG_MIN) &&

        (fabs(startTouchPosition.y - currentTouchPosition.y) <=VERT_SWIPE_DRAG_MIN)) {

        if (startTouchPosition.x < currentTouchPosition.x) {

            NSLog(@"swipe to right");

        }

        else {

            NSLog(@"swipe to left");

        }

    }

}

 

- (void)touchesEnded:(NSSet *)toucheswithEvent:(UIEvent *)event {

    startTouchPosition = CGPointZero;

}

 

- (void)touchesCancelled:(NSSet *)toucheswithEvent:(UIEvent *)event {

    startTouchPosition = CGPointZero;

}

       下面的代码是一个更简单的单触摸事件的跟踪,这次则是用来对拖动进行处理。在本次处理中只有touchesMoved:withEvent:方法有实现代码,在实现代码中对本次位置和上次位置取差值。然后使用差值来重新设置视图的位置。

- (void)touchesBegan:(NSSet *)toucheswithEvent:(UIEvent *)event {

}

 

- (void)touchesMoved:(NSSet *)toucheswithEvent:(UIEvent *)event {

    UITouch *touch= [touches anyObject];

    CGPointcurrentTouchPosition = [touch locationInView:self];

    CGPointprevTouchPosition = [touch previousLocationInView:self];

   

    CGRectviewFrame = self.frame;

    float deltaX= currentTouchPosition.x - prevTouchPosition.x;

    float deltaY= currentTouchPosition.y - prevTouchPosition.y;

    viewFrame.origin.x +=deltaX;

    viewFrame.origin.y +=deltaY;

   

    [self setFrame:viewFrame];

   

}

 

- (void)touchesEnded:(NSSet *)toucheswithEvent:(UIEvent *)event {

}

 

- (void)touchesCancelled:(NSSet *)toucheswithEvent:(UIEvent *)event {

}

处理复杂的多点触摸序列

       点击,拖动和滑动只是简单的手势,通常只涉及一次触摸。要对两个或两个以上的触摸事件进行处理则更为复杂。你可能要跟踪在整个触摸阶段的所有触摸,记录被更改的触摸属性以及更改内部状态为合适值。为了能对多触摸事件进行跟踪和处理还需要做如下的事情:

·     设置视图的multipleTouchEnabled属性为YES

·     使用Core Foundation的字典对象(CFDictionaryRef)在事件过程的各阶段中跟踪触摸的改变

       当处理一个多触摸事件时,你经常会存储每个触摸的最初状态以用于以后的比较。作为例子,你将对每个触摸的最后位置与初始位置进行比较。在touchesBegan:withEvent:方法中,你将从locationInView:方法加获得每个触摸的初始位置并将这些位置以UITouch对象的地址作为键值存储到CFDictionaryRef对象中。在touchesEnded:withEvent:方法中,你将从字典对象中取出初始位置值并与当前位置值进行比较(你应该使用CFDictionaryRef对象而非NSDictionary对象,后一个对象对键值进行拷贝操作,但UITouch对象并没有申明用于对象拷贝的NSCopying协议)。

       下面的代码示例了使用CoreFoundation字典对象对初始位置进行存储。

- (void)cacheBeginPointForTouches:(NSSet *)touches {

    if([touches count] > 0) {

        for (UITouch *touchin touches) {

            CGPoint *point= (CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch);

            if (NULL ==point) {

               point = (CGPoint *)malloc(sizeof(CGPoint));

               CFDictionarySetValue(touchBeginPoints, touch, point);

            }

           

           *point = [touch locationInView:self.superview];

        }

    }

}

       下面的代码示例了如何从字典对象中取得初始位置数据并获得对应触摸的当前位置。

 

- (void)comparePointForTouches: (NSSet *)touches {

    NSArray *sortedTouch= [[touches allObjects] sortedArrayUsingSelector:@selector(compareAddress:)];

   

    UITouch*touch1 = [sortedTouch objectAtIndex:0];

    UITouch*touch2 = [sortedTouch objectAtIndex:1];

   

    CGPointbeginPoint1 = *((CGPoint*)CFDictionaryGetValue(touchBeginPoints, touch1));

    CGPointcurrentPoint1 = [touch1 locationInView:self.superview];

   

    CGPointbeginPoint2 = *((CGPoint*)CFDictionaryGetValue(touchBeginPoints, touch2));

    CGPointcurrentPoint2 = [touch2 locationInView:self.superview];

   

    //Compare current and begin point

}

       更具体的操作可参考apple提供的MoveMe示例。

Hit-Testing

       你自己定义的响应者可以使用hit-testing来找到要对与触摸对应的该响应者中的子视图或子层(subLayer)并对其进行合适的处理。要完成该操作可以调用视图的hitTest:withEvent:方法或子层的hitTest:方法或者直接重载其中任一方法。响应者有时会在事件转发之前对hitTest进行处理。

       如果传入hitTest:withEvent:或hitTest:方法的触摸点位置在包括此方法的视图范围之内,则会被忽略。也就是说子视图在其父视图的范围之外则不会收到触摸事件。

       如果你自定的视图有子视图,你必须决定是否在子视图层或父视图层对触摸进行处理。如果子视图没有声明touchesBegan:withEvent:,touchesMoved:withEvent:或touchesEnded:withEvent:方法对触摸进行处理,则相关的事件会传递给它的父视图。对于多次点击或多点触摸都是与首个触摸子视图相联系,所以父视图是不会收到触摸消息的。要确认能收到所有的触摸消息,父视图要重载hitTest:withEvent:方法并返回其自身对象而不是其子视图。

       下面的代码示例用于检测位于自定义视图层上的“info”图片何时被点击

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

    CGPointlocation = [[touches anyObject] locationInView:self];

    CALayer*hitLayer = [[selflayer] hitTest:[selfconvertPoint:location fromView:nil]];

   

    if(hitLayer) {

        [self displayInfo];

    }

}

       下面的代码,一个响应者类(在本例中为UIWindow的子类)重载了hitTest:withEvent:方法。首先返回父类的hit-test视图,如果该视图是其本身,则返回其最后一个子视图。

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    UIView*hitView = [superhitTest:point withEvent:event];

   

    if(hitView == self) {

        return [[selfsubviews] lastObject];

    }

    else {

        returnhitView;

    }

}

转发触摸事件

       事件转发是用于一些应用程序的一种技术。你通过调用另一个响应者对象的事件处理方法将触摸事件进行转发。尽管这是个很有效率的方法,但在使用过程中还有很多需要注意的地方。UIKit Framework中的类并不是设计用于接收不在其范围内的触摸事件,从编程的角度来看,也就是UITouch对象的view属性必须持有一个对framework对象的引用以用来对触摸事件进行处理。如果你想在你的应用程序中有选择的将触摸事件传递给其它响应者,用于接收的响应者必须是你自己定义的UIView的子类。

       例如:一个应用程序有三个自定义的子视图:A,B和C。当用户点击了视图A,应用程序的窗口对象确定所点击的视图是个hit-test视图并向其发送初始触摸事件。根据一定的条件,视图A将事件转发给视图B或C。在这种情况下,视图A,B和C必须注意要使事件能进行转发,视图B和C要能对不在其范围内的触摸进行处理。

       事件转发经常需要分析触摸对象用来确定向何处转发事件。有些方法可以用来进行这样的分析:

       对于一个视图,使用hit-testing对事件进行分析在将其转发到子视图之前

       在UIWindow的自定义子类中重载sendEvent:方法,对触摸进行分析并将其转发给合适的响应者。如果重载了该方法,你就必须调用父类的sendEvent:方法

       设计你的应用其中并不需要对触摸进行分析

   下面的代码示例了第二种方法,自定义一个UIWindow的子类并对sendEvent:方法进行重载。本例中,子类对象将触摸事件转发给一个自定义的”helper”响应者,在该响应者中对相应的视图进行affine变换。

- (void)sendEvent:(UIEvent*)event

{

         for(TransformGesture *gesture in transformGestures) {

        // collect all the touches we care about from theevent

        NSSet*touches = [gesture observedTouchesForEvent:event];

        NSMutableSet *began = nil;

        NSMutableSet *moved = nil;

        NSMutableSet *ended = nil;

        NSMutableSet *cancelled = nil;

       

        // sort the touches by phase so we can handle themsimilarly to normal event dispatch

        for(UITouch *touchin touches) {

            switch([touch phase]) {

               case UITouchPhaseBegan:

                   if (!began)

                        began = [NSMutableSetset];

                        [began addObject:touch];

                        break;

               case UITouchPhaseMoved:

                   if (!moved)

                        moved = [NSMutableSetset];

                        [moved addObject:touch];

                        break;

               case UITouchPhaseEnded:

                   if (!ended)

                        ended = [NSMutableSetset];

                        [ended addObject:touch];

                        break;

               case UITouchPhaseCancelled:

                   if (!cancelled)

                        cancelled = [NSMutableSetset];

                        [cancelled addObject:touch];

                       break;

               default:

                   break;

            }

        }

       

        // call our methods to handle the touches

        if(began)

           [gesture touchesBegan:began withEvent:event];

        if(moved)

            [gesture touchesMoved:moved withEvent:event];

        if(ended)

           [gesture touchesEnded:ended withEvent:event];

        if(cancelled)

           [gesture touchesCancelled:cancelled withEvent:event];

         }

        

         [super sendEvent:event];

}

 

在UIKit视图或控制器的子类中处理触摸事件

       如果你新建一个UIKit中视图或控制器子类(例如:UIImageView或UISwitch)用于改变或扩展其事件处理的行为,则下面的描述你应记在心里:

       与自定义的UIView子类不同,你不需要重载每个事件处理方法

       对于已重载的事件处理方法,必须调用其父类的相应事件处理方法

       不要将事件转发给UIKit framework对象

多点触摸处理最佳实践

       不管你处理触摸或摇动事件,你需要遵循以下所建议的技术和模式。

l  对于触摸事件不要忘记对取消事件的处理

l  如果你在UIView,UIViewController或UIResponser的子类中对事件进行处理,

你必须声明所有的事件处理方法

不要调用父类的相应的事件处理方法

l  如果你在UIKit其它类的子类中对事件进行处理,

你不必须声明所有的事件处理方法

在你声明的事件处理方法中必须调用父类对应的事件处理方法

l  不要将事件转发给UIkit framework中的响应者对象

事件转发到的对象必须是你自定义视图的实例,所有的这些对象都应该关注事件的转发,在触摸事件的转发中可能会收到不在当前视图范围内的触摸事件

l  自定义视图如果通过响应触摸事件对其重画,必须保证在事件处理方法中只对重画状态进行处理,而所有的绘制操作都在drawRect:方法中完成

l  不要显式的将事件发给下一个响应者(nextResponser),而是调用父类的相应方法由UIKit将事件在响应者序列中进行传递

      

 


原创粉丝点击