iOS学习笔记— UIView用户事件响应 - KnightMareFrame
UIView除了负责展示内容给用户外还负责响应用户事件。本章主要介绍UIView用户交互相关的属性和方法。
1、交互相关的属性
userInteractionEnabled 默认是YES ,如果设置为NO则不响应用户事件,并且把当前控件从事件队列中删除。也就是说设置了userInterfaceEnabled属性的视图会打断响应者链导致该view的subview都无法响应事件。
multipleTouchEnabled 默认是NO,如果设置为YES则支持多点触碰。
exclusiveTouch 默认是NO,如果设置为YES则当前UIView会独占整个Touch事件。具体来说就是如果UIView设置了exclusiveTouch属性为YES则当这个UIView成为第一响应者时,在手指离开屏幕前其他view不会响应任何touch事件。
作用举例:UITableView的每个cell都需要使用exclusive,否则同时点击多个cell会触发每个视图的事件响应。手势识别会忽略此属性。
2、触摸响应
了解UIView的触碰响应之前,首先了解在iOS中触碰事件是什么,事件在视图模型中是如何传递的,视图在接收到一个事件是如何响应的。下面介绍触碰事件类UITouch和响应者链来解释事件的工作原理。
在iOS中UITouch类代表触碰事件。当用户触摸屏幕后就会产生相应的事件,所有相关的UITouch对象都被包装在事件中,被程序交由特定的对象处理。UITouch对象包括触碰的详细信息。
UITouch 含有5个属性:
window :触碰产生时所处的窗口,由于窗口可能发生变化,当前所在的窗口不一定是最开始的窗口。
view :触碰产生时所处的视图。由于视图可能发生变化,当前视图也不一定是最初的视图。
tapCount :短时间内轻击(tap)屏幕的次数,可根据tapCount判断单击、双击或更多的轻击。
timestamp :时间戳记录了触碰事件产生或变化时的时间。单位是秒。
phase :触碰事件在屏幕上有一个周期,即触碰开始、触碰点移动、触碰结束,中途取消。通过phase可以查看当前触碰事件在一个周期中所处的状态。UITouchPhase枚举:
UITouchPhaseBegan
UITouchPhaseMoved
UITouchPhaseStationary
UITouchPhaseEnded
UITouchPhaseCancelled
当手指触碰到屏幕,无论是单点还是多点触碰,事件都会开始,直到用户所有的手指都离开屏幕。期间所有的UITouch对象都被封装在UIEvent事件对象中,由程序分发给处理者。事件记录了这个周期中所有触碰对象状态的变化。
只要屏幕被触摸,系统会将诺干个触碰信息封装到UIEvent对象中发送给程序,由管理程序UIApplication对象将事件分发。
响应者对象 就是可以响应事件并对事件作出处理的对象。在iOS中UIResponder类定义了响应者对象的所有方法。UIApplication、UIWindow、UIViewController、UIView以及UIKit中继承自UIView的控件都间接或直接继承自UIResponder类,这些类都可以当做响应者。
响应者链 表示一系列响应者对象组成的事件传递的链条。当确定了第一响应者后,事件交由第一响应者处理,如果第一响应者不处理事件沿着响应者链传递,交给下一个响应者。一般来说,第一响应者是UIView对象或者UIView的子类对象,当其被触摸后事件交由它处理,如果它不处理,事件就会交给它的UIViewController处理(如果存在),然后是它的superview父视图对象,以此类推,直到顶层视图。如果顶层视图不处理则交给UIWindow对象处理,再到UIApplication对象(如果UIApplication继承自UIResponder)。如果整个响应者链都不响应这个事件则该事件被丢弃。
UIView类继承了UIResponder类,要对事件作出处理还需要重写UIResponder类中定义的事件处理函数。根据不同的触碰状态,程序会调用相应的处理函数,这些函数包括:
-(void) touchesBegan:(NSSet *)touches withEvents:(UIEvent *)event;
-(void) touchesMoved:(NSSet *)touches withEvents:(UIEvent *)event;
-(void) touchesEnded:(NSSet *)touches withEvents:(UIEvent *)event;
-(void) touchesCancelled:(NSSet *)touches withEvents:(UIEvent *)event;
这几个方法被调用时,对应了UITouch类中的phase属性的4个枚举值。当触碰被取消,如触碰过程中被来电打断,会调用touchesCancelled:touches:方法。
这些方法在开发中并不需要全部实现,可以根据需要重写特定的方法。这4个方法都有两个相同的参数:NSSet类型的touches和UIEvent类型的event。Touches表示触碰产生的所有的UITouch对象,event表示事件。因为UIEvent包含了整个触碰过程中所有的触碰对象,所以可以调用allTouches 方法获取该事件内所有触碰对象,也可以调用touchesForView;或者touchesForWindows;取出特定视图或者窗口上的触碰对象。在这几个事件中,都可以拿到触碰对象,然后根据其位置、状态、时间属性做逻辑处理。
轻击操作很容易引起歧义,比如用户点击了一次之后,并不知道用户是想单击还是只是双击的一部分,或者点了两次之后并不知道用户是想双击还是继续点击。可以使用延迟调用函数解决这个问题。
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (touch.tapCount == 1)
{
[self performSelector:@selector(setBackground:) withObject:[UIColor blueColor] afterDelay:2];
}
else if(touch.tapCount == 2)
{
[self cancelPreviousPerformRequestsWIthTarget:self selector:@selector(setBackground:) object:[UIColor blueColor]];
self.view.backgroundColor = [UIColor redColor];
}
}
除了触碰事件外UIResponder还提供了运动事件的支持。
运动事件的方法:
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event 摇动事件开始
-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event 摇动事件结束
-(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event 摇动事件被中断
远程事件:
-(void)remoteControlReceivedWithEvent: 音乐后台播放控制的时候会用到
第一响应者的相关函数:
- (BOOL)canBecomeFirstResponder 默认返回NO
- (BOOL)becomeFirstResponder
- (BOOL)canResignFirstResponder 默认返回YES
- (BOOL)resignFirstResponder;
- (BOOL)isFirstResponder
可以通过becomeFirstResponder方法注册成为第一响应者,通过resignFirstResponder方法不成为第一响应者。比如通过这两个方法操作UITextField来控制键盘的现隐藏。
3、手势
UIView关于手势的方法:
-(void) addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 增加一个手势。
-(void) removeGestureRecognizer:(UIGestureRecognizer *)getureRecognizer 删除一个手势。
-(BOOL) gestureRecognizerShouldBegan:(UIGestureRecognizer *)gestureRecognizer 询问是否开始执行该手势,默认返回YES。
手势相比触碰事件的好处是可以直接使用已经定义好的手势,开发者不用自己计算手指移动轨迹。
UIGestureRecognizer 是一个手势基类,提供了简单的手势实现方式。衍生类如下:
UITabGestureRecognizer 轻击手势
UIPinchGestureRecognizer 捏合手势
UIRotationGestureRecognizer 旋转手势
UISwipeGestureRecognizer 轻扫手势
UIPanGestureRecognizer 拖拽手势
UILongPressGestrueRecognizer 长按手势
UIGestureRecognizer主要方法:
-(id) initWithTarget:action: 初始化方法
-(void)addTarget:action:
-(void)removeTarget:action:
主要属性:
UIGestureRecognizerState state 手势识别当前状态
有以下几种情况:
UIGestureRecognizerStatePossibel , 未识别状态
UIGestureRecognizerStateBegan , 手势开始
UIGestureRecognizerStateChanged , 手势改变
UIGestureRecognizerStateEnded , 手势结束
UIGestureRecognizerStateFailured 手势失败,被其他事件中断。
UITabGestureRecognizer 轻击手势任意手指任意次数的点击
属性:
numberOfTapsRequired 点击次数
numberOfTouchesRequired 手指个数
UIPinchGestureRecognizer 捏合或者扩张手势
属性:
scale :初始值为1,两手指距离减少则scale不断变小;两个手指重合则变为0;
velocity :初始值为0,手指移动的相对速度,两手指距离减少为负数,速度越快数值越少;两手指距离变大为整数,速度越快数值越大。
UIRotationGestureRecognizer 旋转手势
属性:
rotation :初始值为0,两手指的旋转弧度,顺时针旋转为正数,逆时针旋转为负数。
velocity :初始值为0手指一动的相对速度,顺时针为正数越快值越大;逆时针为负越快越小。
UISwipGestureRecognizer 轻扫手势,一个手势只能指定一个方向,如果需要指定多个方向需要多个手势
属性:
numberOfTouchesRequired : 手指个数
direction :手势方向,如UISwipeGestureRecognizerDirectionRight 向右
UIPanGestureRecognizer : 拖拽手势,相比轻扫手势,手指与屏幕的交互时间更长。
属性:
mininumNumberOfTouches :默认值为1,最少手指数量
maxnumNumberOfTouches :最大手指数量
UILongPressGestrueRecognizer : 长按手势。
属性:
numberOfTapsRequired :默认值为0,轻击的次数。
numberOfTouchesRequired :默认值是1,手指数量。
mininumPressDuration :默认值为0.5,单位是秒。
allowableMovement :默认值为10,单位是像素。
UIGestureRecognizer 的子类分别有很多手势,通过 不用的手势可以执行不同的操作,下面来介绍下他们的基本使用方法所有手势配置基本相同,只是针对不同的手势里面有部分属性可以设置,比如说tap点进去看他有两个参数可以设置一个是点击次数,和点击手指数可设置。如果不知道这个手势能配置说明参数,那么点击进入相应的.h 文件查看
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(processGestureRecongnizer:)];//点击手势
[tapGesture setNumberOfTouchesRequired:1];
[tapGesture setNumberOfTapsRequired:1];
[_view addGestureRecognizer:tapGesture];
[tapGesture release];
- (void)processGestureRecongnizer:(UIGestureRecognizer *)gesture
{
if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) {
[self positionAnimation];
}
}
#pragma mark -- InitUserInterface
- (void)initUserInterface
{
_view = [[UIView alloc]init];
[_view setBounds:CGRectMake(0, 0, 200, 200)];
[_view setCenter:CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds))];
[_view setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:_view];
//两手指拨动手势
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(processGestureRecognizer:)];
[pinch setDelegate:self];//设置代理
[_view addGestureRecognizer:pinch]; //对view添加这个手势
[pinch release];
//旋转手势
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(processGestureRecognizer:)];
[rotation setDelegate:self];
[_view addGestureRecognizer:rotation];
[rotation release];
//长按手势
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(processGestureRecognizer:)];
longPress.minimumPressDuration = 2;
[_view addGestureRecognizer:longPress];
[longPress release];
//滑动手势--左
UISwipeGestureRecognizer *leftSwipe = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(processGestureRecognizer:)];
[leftSwipe setDirection:UISwipeGestureRecognizerDirectionLeft];//配置滑动方向
[self.view addGestureRecognizer:leftSwipe];
[leftSwipe release];
//滑动手势--右
UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(processGestureRecognizer:)];
[rightSwipe setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:rightSwipe];
[rightSwipe release];
//拖移手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(processGestureRecognizer:)];
[pan setDelegate:self];
[_view addGestureRecognizer:pan];
[pan release];
}
#pragma mark -- GestureRrecognizer methods
//代理方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return NO;
}
// 手势action 让对应的手势执行相应地操作
- (void)processGestureRecognizer:(UIGestureRecognizer *)gesture
{
//判断手势类型
if ([gesture isKindOfClass:[UIPinchGestureRecognizer class]])
{
UIPinchGestureRecognizer *pinch = (UIPinchGestureRecognizer *)gesture;
static float lastScale;//静态变量记录上次大小
if (pinch.state == UIGestureRecognizerStateBegan) {
lastScale = pinch.scale;// 手势开始把初始scale赋给静态变量方便更新
}else if (pinch.state == UIGestureRecognizerStateChanged){
[_view setTransform:CGAffineTransformScale(_view.transform, 1+(pinch.scale - lastScale), 1+(pinch.scale - lastScale))];//让View进行动态的放大或缩小
lastScale = pinch.scale;// 更新scale的值 --(这样做让view保持变化后的状态而不会是初始状态)
//此方法不需要更新lastScale的值 都是从原型开始
// [_view setTransform:CGAffineTransformMakeScale(1+(pinch.scale - lastScale), 1+(pinch.scale - lastScale))];
}
}else if ([gesture isKindOfClass:[UIRotationGestureRecognizer class]])
{
UIRotationGestureRecognizer *rotation = (UIRotationGestureRecognizer *)gesture;
static float lastRotation;
if (rotation.state == UIGestureRecognizerStateBegan) {
lastRotation = rotation.rotation;
}else if (rotation.state == UIGestureRecognizerStateChanged){
[_view setTransform:CGAffineTransformRotate(_view.transform, rotation.rotation - lastRotation)];
lastRotation = rotation.rotation;
}
}
else if ([gesture isKindOfClass:[UILongPressGestureRecognizer class]])
{
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)gesture;
if (longPress.state == UIGestureRecognizerStateBegan) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"Long Press Begin" delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil];
[alert show];
[alert release];
}
}
else if ([gesture isKindOfClass:[UISwipeGestureRecognizer class]])
{
UISwipeGestureRecognizer *swipe = (UISwipeGestureRecognizer *)gesture;
if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) {
[UIView animateWithDuration:1 animations:^{
[_view setCenter:CGPointMake(CGRectGetMinX(self.view.bounds), CGRectGetMidY(self.view.bounds))];
}];
}else{
[UIView animateWithDuration:1 animations:^{
[_view setCenter:CGPointMake(CGRectGetMaxX(self.view.bounds), CGRectGetMidY(self.view.bounds))];
}];
}
}
else if ([gesture isKindOfClass:[UIPanGestureRecognizer class]])
{
UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gesture;
static CGPoint lastLocation;
if (pan.state == UIGestureRecognizerStateBegan) {
lastLocation = _view.center;
}else if (pan.state == UIGestureRecognizerStateChanged)
{
CGPoint translationPoint = [pan translationInView:self.view];
_view.center = CGPointMake(translationPoint.x + lastLocation.x, translationPoint.y+ lastLocation.y);
}else if (pan.state == UIGestureRecognizerStateEnded)
{
lastLocation = CGPointZero;
}
}
}