Event Handling Guide for iOS--事件驱动指南
事件是发送给应用程序来通知它用户动作的对象。在iOS中,事件可以有多种形式:多触摸事件,motion(,移动,手 势)事件---例如,设备的加速计(accelerometer)--和控制多媒体的事件。(最后一种类型的事件被称为一个远程控制事件因为它起始于一个 耳机或其他外部访问配件)。如下图所示:
在iOS中,UIKit和Core Motion框架负责事件传播和投递。
一、事件类型和投递:
一
个iPhone、iPad或者iPod
touch设备有多种方式集成输入流数据给应用程序访问。多触摸技术直接操作视图,包括虚拟键盘。三个方向上的加速计。GPS和方向。这些设备在检测到触
摸、设备移动、和定位更改时,处理传送给系统框架的基础数据。框架把数据打包并投递他们到应用程序作为事件来处理。
1,UIKit事件对象和类型:
很多事件都是UIKit框架中的UIEvent的实例.UIEvent对象可能封装了事件的相关状态,例如关联的触摸。它还记录事件产生的时间。
目前,UIKit可以识别3种类型的事件:触摸事件、“shaking"(晃动)motion事件,和remote-control事件。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;
每个事件都有一个事件类型和子类型,通过type和subtype属性来获得。触摸事件的subtype总是UIEventSubtypeNone.
你永远也不要retain一个UIEvent对象。如果你需要保留事件对象的当前状态,你应该用适当的方式拷贝和存储这些状态,例如用字典存储这些值。
运行iOS的设备可以发送另外一些类型的事件,它们不是UIEvent对象,但仍然封装了一些硬件信息。
2,事件投递:
当
用户触摸设备时,iOS识别这组触摸并打包到一个UIEvent对象,并将其放入到活动的应用程序的事件队列中。如果系统认为设备的摇动是一个
motion
event,一个代表该事件的对象也被放入到应用的事件队列中。单例UIApplication对象从队列顶部取出一个事件并分发其以处理。一般,它发送
事件给应用的关键window--用户事件的焦点窗口。
3,响应者对象和响应者链:
二,多触摸事件:
1,事件和触摸:一个type属性为UIEventTypeTouches的UIEvent对象代表一个触摸事件。当手指触摸屏幕并移动于其表面时,系统持续的发送这些触摸事件对象到一个应用中。事件提供了所有触摸的快照。
2,触摸事件的投递:
1)默认地,一个视图接收触摸事件,如果你设置其userInteractionEnabled属性为NO,那么会关闭触摸事件的投递。一个视图在隐藏或透明时也不能接收触摸事件。
2)一个应用可以调用UIApplication的beginIgnoringInteractionEvents并在之后调用endIgnoringInteractionEvents方法。
3)默认地,一个视图在一个多触摸序列中,只识别一个手指的触摸事件。如果你想视图处理多触摸,你必须设置mutipleTouchEnabled属性为YES。
4)默认地,一个视图的exclusiveTouch属性为NO,这意味着这个视图不阻塞其他view接收触摸。如果你设置这个属性为YES,如果你正在跟踪触摸,那么只有这个view可以跟踪触摸。其他view不能接收这些触摸信息。然而,这个视图也不接收和其他视图关联的触摸信息。
5)UIView的子类可以重载hitTest:withEvent:来限制多触摸事件投递到其子视图中。
3,处理多触摸事件:
必须是UIResponder的子类,可以是:UIView、UIViewController、UIImageView、UIApplication或UIWindow。处理事件的方法:
- (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
每个UITouch对象都有一个触摸阶段,例如,touchesBegan:关联UITouchPhaseBegan,你可以通过UItouch对象的phase属性来获得。
4,触摸事件的基本处理:
通过NSSet对象的anyObject消息来获得任意的一个触摸信息。
UITouch的一个重要的方法locationInView:。一组相应的方法:previousLocationInView:
tapCount获得轻击次数。
allTouches获得所有的触摸信息,因为一个触摸事件的所有触摸信息不一定都在一个View中,所以第一个参数的触摸NSSet应该是关联该view的触摸信息。如下图所示:
可以给UIEvent对象发送touchesForWindow:消息、touchesForView:消息。
5,处理轻击手势:如果你想处理单击手势和双击手势,你可以在你的触摸事件中处理双击如下所示:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
if (touch.tapCount >= 2) {
[self.superview bringSubviewToFront:self];
}
}
}
然后添加一个单击手势识别。
或者:
1)在触摸结束后执行 performSelector:withObject:afterDelay:,这个selector代表着单击手势要执行的操作。
2)在下一次触摸开始时 ,检查tapCount是否是2,如果是,可以 cancelPreviousPerformRequestsWithTarget:方法。如果不是那就识别为单击事件。如下所示:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *aTouch = [touches anyObject];
if (aTouch.tapCount == 2) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *theTouch = [touches anyObject];
if (theTouch.tapCount == 1) {
NSDictionary *touchLoc = [NSDictionary dictionaryWithObject:
[NSValue valueWithCGPoint:[theTouch locationInView:self]] forKey:@"location"];
[self performSelector:@selector(handleSingleTap:) withObject:touchLoc afterDelay:0.3];
} else if (theTouch.tapCount == 2) {
// Double-tap: increase image size by 10%"
CGRect myFrame = self.frame;
myFrame.size.width += self.frame.size.width * 0.1;
myFrame.size.height += self.frame.size.height * 0.1;
myFrame.origin.x -= (self.frame.origin.x * 0.1) / 2.0;
myFrame.origin.y -= (self.frame.origin.y * 0.1) / 2.0;
[UIView beginAnimations:nil context:NULL];
[self setFrame:myFrame];
[UIView commitAnimations];
}
}
- (void)handleSingleTap:(NSDictionary *)touches {
// Single-tap: decrease image size by 10%"
CGRect myFrame = self.frame;
myFrame.size.width -= self.frame.size.width * 0.1;
myFrame.size.height -= self.frame.size.height * 0.1;
myFrame.origin.x += (self.frame.origin.x * 0.1) / 2.0;
myFrame.origin.y += (self.frame.origin.y * 0.1) / 2.0;
[UIView beginAnimations:nil context:NULL];
[self setFrame:myFrame];
[UIView commitAnimations];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
/* no state to clean up, so null implementation */
}
6,处理轻扫和Drag手势:
//检测轻扫
#define HORIZ_SWIPE_DRAG_MIN 12
#define VERT_SWIPE_DRAG_MAX 4
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
// startTouchPosition is an instance variable
startTouchPosition = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self];
// To be a swipe, direction of touch must be horizontal and long enough.
if (fabsf(startTouchPosition.x - currentTouchPosition.x) >= HORIZ_SWIPE_DRAG_MIN &&
fabsf(startTouchPosition.y - currentTouchPosition.y) <= VERT_SWIPE_DRAG_MAX)
{
// It appears to be a swipe.
if (startTouchPosition.x < currentTouchPosition.x)
[self myProcessRightSwipe:touches withEvent:event];
else
[self myProcessLeftSwipe:touches withEvent:event];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
startTouchPosition = CGPointZero;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
startTouchPosition = CGPointZero;
}
//检测Drag手势
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *aTouch = [touches anyObject];
CGPoint loc = [aTouch locationInView:self];
CGPoint prevloc = [aTouch previousLocationInView:self];
CGRect myFrame = self.frame;
float deltaX = loc.x - prevloc.x;
float deltaY = loc.y - prevloc.y;
myFrame.origin.x += deltaX;
myFrame.origin.y += deltaY;
[self setFrame:myFrame];
}
7,处理混合多触摸手势:
8,Hit-Testing:
9,转发触摸事件:
三、手势识别者:
1,UIGestureRecognizer是一个抽象类,具体使用的是其子类:UITapGestureRecognizer、
UIPinchGestureRecognizer(缩放)、UIPanGestureRecognizer(Panning or
dragging)、UISwipeGestuerRecognizer(任意方向的轻扫)、
UIRotationGestureRecognizer(旋转)、UILongPressGestureRecognizer(长按)
2,手势被附加到一个view上:对于一些手势,通过UIGestureRecognizer的locationInView:和locationOfTouch:inView:方法来使得客户端查找手势的位置。
3,手势触发动作消息:
当手势识别者识别其手势后,它发送一个或多个动作消息给一个或多个目标。当你创建一个识别者时,你初始化一个动作和一个目标,你之后可以增加更多的目标-动作对(通过addTarget:action)
4,离散的手势和持续的手势:
当识别者识别手势时,它或者发送一个单独的动作消息 或者发送多个动作消息,直到手势停止。
5,实现手势识别:例子:
UITapGestureRecognizer
*doubleFingerDTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleDoubleDoubleTap:)];
selector函数最后有一个sender参数,可以把UIGestureRecognizer传进去
创建完UIGestureRecognizer之后,你需要使用view的addGestureRecognizer:, 使用gestureRecognizers属性读取,使用removeGestureRecognizer:移除。
doubleFIngerDTap.numberOfTapsRequired=2;
self.theView addGesture ...
6,响应手势:
离散的手势略去;持续的手势,在手势处理方法中,目标对象经常获取额外的关于手势的信息。例如scale属性或velocity(速率)属性。
例子:处理缩放手势和处理Panning手势:
- (IBAction)handlePinchGesture:(UIGestureRecognizer *)sender {
CGFloat factor = [(UIPinchGestureRecognizer *)sender scale];
self.view.transform = CGAffineTransformMakeScale(factor, factor);
}
- (IBAction)handlePanGesture:(UIPanGestureRecognizer *)sender {
CGPoint translate = [sender translationInView:self.view];
CGRect newFrame = currentImageFrame;
newFrame.origin.x += translate.x;
newFrame.origin.y += translate.y;
sender.view.frame = newFrame;
if (sender.state == UIGestureRecognizerStateEnded)
currentImageFrame = newFrame;
}
7,与其他的手势交互:
可能不只一个手势被添加到一个view上,默认行为中,一个多触摸序列中的触摸事件从一个手势识别者到另一个识别者,并且没有固定顺序,直到事件最终被投递给view。经常这种默认行为是你想要的。但有时你可能需要另外的行为:
1)让某个识别者在另外一个识别者可以开始分析触摸事件之前先失败。
2)阻止其他识别者分析一个特定的多触摸序列或那个序列中的一个触摸对象
3)允许两个手势识别者同时操作。
UIGestureRecognizer提供了客户端方法、代理方法和子类化重载方法来使你更改这些行为。
8,需要一个手势识别者失败:
你调用 requireGestureRecognizerToFail:。
在发送消息之后,接收的手势识别者必须保持UIGestureRecognizerStatusPossible状态,直到指定的手势识别者的状态转到
UIGestureRecognizerStateFailed。如果指定的手势状态转到
UIGestureRecognizerStateRecognized或UIGestureRecognizerStateBegan。那么接收的识别
者可以开始,但如果它识别了它的手势,也没有动作消息发送。
9,阻止手势识别者分析触摸信息:
通过代理方法或子类化方法。
UIGestureRecognizerDelegate协议声明了两个可选的方法来阻止指定手势识别者识别手势on a base-by-base basis:
1)
gestureRecognizerShouldBegin:
这个方法在一个识别者尝试转变出状态UIGestureRecognizerStatusPossible时被调用。返回NO使其转到
UIGestureRecognizerStateFailed。默认为YES。
2)gestureRecognizer:shouldReceiveTouch: 这个方法在window对象调用识别者的touchesBegan:withEvent:之前被调用。返回NO表示阻止识别者看到表示触摸的对象。默认值为YES。
另外,有两个UIGestureRecognizer的方法影响相同的行为:
-(BOOL)canPreventGestureRecognizer:
-(BOOL)canBePreventedByGestureRecognizer:
10,允许手势识别同时进行:
默
认地,没有两个手势识别者可以同事尝试识别他们的手势。但是你可以通过实现代理的可选方法:
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法来
更改这个行为。返回YES表示允许两个手势识别者同事识别他们的手势。
注意:返回YES表示允许,返回NO并不表示阻止同时识别因为另外的手势的代理可能返回YES。
11,事件投递给Views:
12,影响投递触摸到Views的属性:
你可以更改UIGestureRecognizer的3个属性来更改默认投递路径:
1) cancelsTouchesInView (默认为YES)
2) delaysTouchesBegan (默认为NO)
3)delaysTouchesEnded (默认为YES)
如果你更改这些属性的值,
1)cancelsTouchesInView设置为NO,导致touchesCancalled:withEvent: 不会被发送给view 任何触摸--在识别手势期间。结果是,之前被附加的view在开始或移动阶段接收的任何触摸对象都是无效的。
2)delaysTouchesBegan 设置为YES。 .。。
3) delaysTouchesEnded设置为NO。 .。。
13,创建自己的手势识别者:
1)状态转换:
2)实现一个自定义的手势识别者:
转载自: http://supershll.blog.163.com/blog/static/37070436201273113448804/