iOS事件处理指南-手势识别器(译)

手势识别器(Gesture Recognizers)

手势识别器将低级别的事件处理代码转换成高级别的动作。它们是你绑定到视图上的对象,这些对象允许视图对动作进行响应,就像控件一样。手势识别器把触摸解析成一个确定的手势,例如轻拂(swip),捏合(pinch),或者旋转。如果它们识别出了被分配手势,会发送一条动作消息给一个目标对象。目标对象很典型是视图的视图控制器,视图控制器像图1-1展示的那样对手势进行响应。这种设计模式简单而又强大;你能够动态的决定一个视图要响应哪个动作,并且你能够给一个视图加上手势识别器而不用创建视图的子类。


手势识别器绑定视图
手势识别器绑定视图

使用手势识别器来简化事件处理

UIKit framework提供了能检测到常见手势的预定义手势识别器。如果可能,使用预定义的手势识别器是最好的方式,因为预定义手势识别器的简单减少了你需要写的代码数量。并且,使用一个标准的手势识别器来代替你自己定义,能保证你的应用的行为符合用户的预期。

如果想让你的应用识别一个独特的手势,比如打个对号或者一个旋转手势,你可以创建自定义的手势识别器。想要学习如何设计和完成你自己的手势识别器,请参见Creating a Custom Gesture Recognizer

内建的手势识别器识别常见的手势

在你设计应用的时候,你需要考虑你想识别哪些手势。然后,对于每一个手势,你需要决定下表1-1中的预定义手势识别器哪一个够用。

手势 UIKit 类
Tapping (any number of taps) UITapGestureRecognizer
Pinching in and out (for zooming a view) UIPinchGestureRecognizer
Panning or dragging UIPanGestureRecognizer
Swiping (in any direction) UISwipeGestureRecognizer
Rotating (fingers moving in opposite directions) UIRotationGestureRecognizer
Long press (also known as “touch and hold”) UILongPressGestureRecognizer

你的应用应该只以用户期望的方式对手势进行反馈。例如,一个pinch应该放大缩小,一个点击应该选择某样东西。For guidelines about how to properly use gestures, see Apps Respond to Gestures, Not Clicks.

手势识别器与视图绑定

每个手势识别器都是和一个视图联系起来的。相比之下,一个的视图能拥有多个视图控制器,因为一个独立的视图能响应多个手势识别器。如果你想要一个手势识别器识别发生在一个特定视图上的触摸,你得把这个手势识别器绑定到这个视图上去。当用户触摸到这个视图时,手势识别器将早于视图对象收到一条触摸发生的消息。因此,这个手势识别器能够代表视图对触摸进行响应。

手势触发动作消息

当一个手势识别器识别出了一个特殊手势,它将发送一条动作消息给它的目标。要创建一个手势识别器,你得对它进行初始化,设置一个目标(target)和一个动作(action)。

离散和连续的手势

手势不是离散的就是连续的。一个离散的手势,例如点击(tap),发生一次。一个连续的手势,例如捏合(pinching),发生在一个时间段内。对于离散的手势,一个手势识别器发送给它的目标一个独立的动作消息。而连续手势的手势识别器会持续发送给目标动作消息,直到触摸序列停止,如图1-2所示。


离散和连续的手势
离散和连续的手势

使用手势识别器对事件响应

为你的应用添加一个内建的手势识别器需要做三件事:

  1. 创建并配置一个手势识别器实例;
    这一步包括指定一个目标,动作,和手势的一些特殊属性(如点击次数);
  2. 把这个手势识别器绑定到视图上;
  3. 完成处理这个手势的动作方法(action method)。

使用界面构建器(IB)添加手势识别器

在Xcode的IB中,添加一个手势识别器和添加一个任何一个对象到界面上方式相同——从对象库中拖拽一个手势识别器到一个视图上。你做完这些以后,手势识别器会自动地绑定到这个视图上。你可以检查你的手势识别器绑定到了哪个视图,并且,如果必要的话你可以改变nib文件中的连接。

创建完手势识别器对象之后,你需要创建并连接一个动作方法。这个方法将在连接的手势识别器识别出它的手势时被调用。如果你需要在这个动作方法之外引用这个手势识别器,你应该为这个手势识别器再创建并连接一个接口。你的代码应和清单1-1很像。

Listing 1-1 Adding a gesture recognizer to your app with Interface Builder

@interface APLGestureRecognizerViewController ()
@property (nonatomic, strong) IBOutlet UITapGestureRecognizer *tapRecognizer;
@end
 
@implementation
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer
     // Will implement method later...
}
@end

使用代码添加手势识别器

你也可以通过alloc和init一个具体的UIGestureRecognizer子类(例如UIPinchGestureRecognizer)的实例。当你初始化手势识别器的时候,得指定一个目标对象和一个动作选择器(selector),像清单1-2那样。目标对象常常是视图的视图控制器。

如果通过代码建立一个手势识别器,你需要使用addGestureRecognizer: 方法把它绑定到视图上。清单1-2创建了一个独立的点击手势识别器,指定了需要识别的手势是一次点击,然后把手势识别器对象绑定到了一个视图上。典型的做法是,你在视图控制器的viewDidLoad方法中创建手势识别器,如清单1-2展示。

Listing 1-2 Creating a single tap gesture recognizer programmatically

- (void)viewDidLoad {
     [super viewDidLoad];
 
     // Create and initialize a tap gesture
     UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
          initWithTarget:self action:@selector(respondToTapGesture:)];
 
     // Specify that the gesture must be a single tap
     tapRecognizer.numberOfTapsRequired = 1;
 
     // Add the tap gesture recognizer to the view
     [self.view addGestureRecognizer:tapRecognizer];
 
     // Do any additional setup after loading the view, typically from a nib
}

对离散手势的响应

你在创建一个手势识别器的时候会把识别器连接到一个动作方法上。使用这个方法对你的手势识别器进行响应。清单1-3提供一个对离散手势进行响应的例子。当用户点击手势识别器绑定的视图时,视图控制器显示一张写有“Tap.”的图片。showGestureForTapRecognizer:方法会确定视图上手势的位置,这个位置信息来自识别器的locationInView属性,然后把图片在这个位置上显示出来。


Note:下面的三个代码案例都来自于Simple Gesture Recognizers案例项目,你可以查看更多的上下文。


Listing 1-3 Handling a double tap gesture

- (IBAction)showGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer {
       // Get the location of the gesture
      CGPoint location = [recognizer locationInView:self.view];
 
       // Display an image view at that location
      [self drawImageForGestureRecognizer:recognizer atPoint:location];
 
       // Animate the image view so that it fades out
      [UIView animateWithDuration:0.5 animations:^{
           self.imageView.alpha = 0.0;
      }];
}

每一个手势识别器都有自己的属性集合。例如,在清单1-4中,showGestureForSwipeRecognizer:方法使用了swipe手势识别器的方向属性来确定用户是往左滑还是往右滑。然后,它使用这个值来使图片从滑动方向逐渐消失掉。

Listing 1-4 Responding to a left or right swipe gesture

  // Respond to a swipe gesture
  - (IBAction)showGestureForSwipeRecognizer:(UISwipeGestureRecognizer *)recognizer {
         // Get the location of the gesture
         CGPoint location = [recognizer locationInView:self.view];
   
         // Display an image view at that location
         [self drawImageForGestureRecognizer:recognizer atPoint:location];
   
         // If gesture is a left swipe, specify an end location
         // to the left of the current location
         if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
              location.x -= 220.0;
         } else {
              location.x += 220.0;
         }
   
         // Animate the image view in the direction of the swipe as it fades out
         [UIView animateWithDuration:0.5 animations:^{
              self.imageView.alpha = 0.0;
              self.imageView.center = location;
         }];
  }

对连续手势的响应

连续手势允许应用对正在发生的手势进行响应。例如,当用户pinching的时候应用界面可以缩放,或者允许在屏幕内对对象进行拖拽。

清单1-5展示了一个和用户手势相同角度的“旋转”图片,并且当用户停止旋转时,showGestureForRotationRecognizer:方法将被持续地调用,直到手指抬起。

Listing 1-5 Responding to a rotation gesture

// Respond to a rotation gesture
- (IBAction)showGestureForRotationRecognizer:(UIRotationGestureRecognizer *)recognizer {
       // Get the location of the gesture
       CGPoint location = [recognizer locationInView:self.view];
 
       // Set the rotation angle of the image view to
       // match the rotation of the gesture
       CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]);
       self.imageView.transform = transform;
 
       // Display an image view at that location
       [self drawImageForGestureRecognizer:recognizer atPoint:location];
 
      // If the gesture has ended or is canceled, begin the animation
      // back to horizontal and fade out
      if (([recognizer state] == UIGestureRecognizerStateEnded) || ([recognizer state] == UIGestureRecognizerStateCancelled)) {
           [UIView animateWithDuration:0.5 animations:^{
                self.imageView.alpha = 0.0;
                self.imageView.transform = CGAffineTransformIdentity;
           }];
      }
 
}

方法每一次被调用时,图片都drawImageForGestureRecognizer:方法被设置成不透明的。当手势完成的时候,图片被animateWithDuration:方法设置为透明的。showGestureForRotationRecognizer:方法通过检查手势识别器的状态来确定手势是不是完成了。这些状态在一个有限状态机中的手势识别器中有更详细的解释。

定义手势识别器如何交互

很多时候,当你把手势识别器加到你的应用中的时候,你得很清楚你想让你的识别器(或者触摸事件代码)如何互相交互。因此,你首先要懂一点手势识别器的工作方式。

手势识别器在有限状态机中的运作

手势识别器以预定设定好的方式从一个状态迁移到另一个状态。处于各个状态时,它们能够迁移到一个或多个可能的下一个状态,这都是基于确定的情况。状态机的变化情况,取决于这个手势识别器是离散的还是连续的,就像图1-3表示的那样。所有的手势识别器开始于“不确定”状态(UIGestureRecognizerStatePossible)。它们分析收到的所有的多面触摸序列,并且在分析过程中要么是识别出来,要么识别手势失败。识别手势失败意思是手势识别器迁移到“失败”状态(UIGestureRecognizerStateFailed)。

State machines for gesture recognizers
State machines for gesture recognizers

当一个离散的手势识别器识别出来了它的手势,这个手势识别器会从“不确定”状态迁移到“已识别”状态(UIGestureRecognizerStateRecognized) 并且整个识别完成。

对于连续的手势,当手势识别器第一次识别出手势时,该识别器会从“不确定”状态迁移到“开始”状态(UIGestureRecognizerStateRecognized)。然后,它会从“开始”状态迁移到“改变”状态(UIGestureRecognizerStateChanged),并且在手势发生时持续地从“改变”改变迁移到“改变”状态。当用户的最后一个手指从视图上举起离开的时候,手势识别器会迁移到“结束”状态(UIGestureRecognizerStateEnded)。这个手势识别至此完成。注意,“结束”状态其实是“已识别”状态的同义词。

如果一个连续手势的识别器判断确定当前手势不再符合期望的模式,那么它也可以从“改变”状态迁移到“取消”状态(UIGestureRecognizerStateCancelled)。

手势识别器每次改变状态时,它会向它的目标发送一条消息,除非它迁移到了“失败”状态或“取消”状态。因此,一个离散的手势识别器在它从“不确定”迁移到“已识别”状态时,只会发送一条动作消息。一个连续的手势识别器在它改变状态时,会发送很多条动作消息。

当一个手势识别器迁移到了“已识别”(或“结束”)状态时,它将重置它的状态到“不确定”。状态往回迁移到“不确定”的动作不会触发动作消息。

手势识别器间的相互作用

一个视图可以绑定多个手识别器。使用该视图的gestureRecognizers属性来查看视图绑定了哪些手势识别器。你也可以动态地改变视图处理手势的方式,可以添加(addGestureRecognizer:)或去掉(removeGestureRecognizer:)某个手势识别器。

当一个视图绑定了多个的手势识别器时,你可能想要改变手势识别器接收和分析触摸事件的竞争方式。默认情况下,手势识别器没有一个设定的顺序决定哪个识别器先接收到触摸。由于这个缘故,每次触摸,触摸传递到各个手势识别器的顺序都不尽相同。你可以覆写这个默认的行为:

  • 规定一个手势识别器应在另一个之前对触摸进行分析。
  • 允许两个手势识别器同时进行运作。
  • 阻止一个手势识别器分触摸。

使用UIGestureRecognizer类方法,代理方法,以及子类覆写的方法,来使这些行为生效。

Declare ing a Specific Order for Two Gesture Recognizers
给两个手势识别器声明一个特殊的顺序
Imagine that you want to recognize a swipe and a pan gesture, and you want these two gestures to trigger distinct actions. By default, when the user attempts to swipe, the gesture is interpreted as a pan. This is because a swiping gesture meets the necessary conditions to be interpreted as a pan (a continuous gesture) before it meets the necessary conditions to be interpreted as a swipe (a discrete gesture).

想象你想识别一个swipe或一个pan手势,

For your view to recognize both swipes and pans, you want the swipe gesture recognizer to analyze the touch event before the pan gesture recognizer does. If the swipe gesture recognizer determines that a touch is a swipe, the pan gesture recognizer never needs to analyze the touch. If the swipe gesture recognizer determines that the touch is not a swipe, it moves to the Failed state and the pan gesture recognizer should begin analyzing the touch event.

You indicate this type of relationship between two gesture recognizers by calling the requireGestureRecognizerToFail: method on the gesture recognizer that you want to delay, as in Listing 1-6. In this listing, both gesture recognizers are attached to the same view.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容