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閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容