iphone開發之觸摸事件詳解

現在的應用中大都支持觸摸操作,如果應用中加入觸摸事件會提高用戶體驗性。今天我們就來學習ios下觸摸事件,先來看官方說明文檔:

觸摸事件

iPhone OS中的觸摸事件基於多點觸摸模型。用戶不是通過鼠標和鍵盤,而是通過觸摸設備的屏幕來操作對象、輸入數據、以及指示自己的意圖。iPhone OS將一個或多個和屏幕接觸的手指識別爲多點觸摸序列的一部分,該序列從第一個手指碰到屏幕開始,直到最後一個手指離開屏幕結束。iPhone OS通過一個多點觸摸序列來跟蹤與屏幕接觸的手指,記錄每個手指的觸摸特徵,包括手指在屏幕上的位置和發生觸摸的時間。應用程序通常將特定組合的觸摸識別爲手勢,並以用戶直覺的方式來進行響應,比如對收縮雙指距離的手勢,程序的響應是縮小顯示的內容;對輕拂屏幕的手勢,則響應爲滾動顯示內容。

請注意:手指在屏幕上能達到的精度和鼠標指針有很大的不同。當用戶觸擊屏幕時,接觸區域實際上是橢圓形的,而且比用戶想像的位置更靠下一點。根據觸摸屏幕的手指、手指的尺寸、手指接觸屏幕的力量、手指的方向、以及其它因素的不同,其“接觸部位”的尺寸和形狀也有所不同。底層的多點觸摸系統會分析所有的這些信息,爲您計算出單一的觸點。

很多UIKit類對多點觸摸事件的處理方式不同於它的對象實例,特別是像UIButtonUISlider這樣的UIControl的子類。這些子類的對象—被稱爲控件對象—只接收特定類型的手勢,比如觸擊或向特定方向拖拽。控件對象在正確配置之後,會在某種手勢發生後將動作消息發送給目標對象。其它的UIKit類則在其它的上下文中處理手勢,比如UIScrollView可以爲表格視圖和具有很大內容區域的文本視圖提供滾動行爲。

某些應用程序可能不需要直接處理事件,它們可以依賴UIKit類實現的行爲。但是,如果您創建了UIView定製子類—這是iPhone OS系統開發的常見模式—且希望該視圖響應特定的觸摸事件,就需要實現處理該事件所需要的代碼。而且,如果您希望一個UIKit對象以不同的方式響應事件,就必須創建框架類的子類,並重載相應的事件處理方法。

事件和觸摸

在iPhone OS中,觸摸動作是指手指碰到屏幕或在屏幕上移動,它是一個多點觸摸序列的一部分。比如,一個pinch-close手勢就包含兩個觸摸動作:即屏幕上的兩個手指從相反方向靠近對方。一些單指手勢則比較簡單,比如觸擊、雙擊、或輕拂(即用戶快速碰擦屏幕)。應用程序也可以識別更爲複雜的手勢,舉例來說,如果一個應用程序使用具有轉盤形狀的定製控件,用戶就需要用多個手指來“轉動”轉盤,以便進行某種精調。

事件是當用戶手指觸擊屏幕及在屏幕上移動時,系統不斷髮送給應用程序的對象。事件對象爲一個多點觸摸序列中所有觸摸動作提供一個快照,其中最重要的是特定視圖中新發生或有變化的觸摸動作。一個多點觸摸序列從第一個手指碰到屏幕開始,其它手指隨後也可能觸碰屏幕,所有手指都可能在屏幕上移動。當最後一個手指離開屏幕時,序列就結束了。在觸摸的每個階段,應用程序都會收到事件對象。

觸摸信息有時間和空間兩方面,時間方面的信息稱爲階段(phrase),表示觸摸是否剛剛開始、是否正在移動或處於靜止狀態,以及何時結束—也就是手指何時從屏幕舉起(參見圖3-1)。觸摸信息還包括當前在視圖或窗口中的位置信息,以及之前的位置信息(如果有的話)。當一個手指接觸屏幕時,觸摸就和某個窗口或視圖關聯在一起,這個關聯在事件的整個生命週期都會得到維護。如果有多個觸摸同時發生,則只有和同一個視圖相關聯的觸摸會被一起處理。類似地,如果兩個觸摸事件發生的間隔時間很短,也只有當它們和同一個視圖相關聯時,纔會被處理爲多觸擊事件。

圖3-1 多點觸摸序列和觸摸階段

A multi-touch sequence and touch phases

在iPhone OS中,一個UITouch對象表示一個觸摸,一個UIEvent對象表示一個事件。事件對象中包含與當前多點觸摸序列相對應的所有觸摸對象,還可以提供與特定視圖或窗口相關聯的觸摸對象(參見圖3-2)。在一個觸摸序列發生的過程中,對應於特定手指的觸摸對象是持久的,在跟蹤手指運動的過程中,UIKit會對其進行修改。發生改變的觸摸屬性變量有觸摸階段、觸摸在視圖中的位置、發生變化之前的位置、以及時間戳。事件處理代碼通過檢查這些屬性的值來確定如何響應事件。

圖3-2 UIEvent對象及其UITouch對象間的關係

Relationship of a UIEvent object and its UITouch objects

系統可能隨時取消多點觸摸序列,進行事件處理的應用程序必須做好正確響應的準備。事件的取消可能是由於重載系統事件引起的,電話呼入就是這樣的例子。

事件的傳遞

系統將事件按照特定的路徑傳遞給可以對其進行處理的對象。如“核心應用程序架構”部分描述的那樣,當用戶觸摸設備屏幕時,iPhone OS會將它識別爲一組觸摸對象,並將它們封裝在一個UIEvent對象中,放入當前應用程序的事件隊列中。事件對象將特定時刻的多點觸摸序列封裝爲一些觸摸對象。負責管理應用程序的UIApplication單件對象將事件從隊列的頂部取出,然後派發給其它對象進行處理。典型情況下,它會將事件發送給應用程序的鍵盤焦點窗口—即擁有當前用戶事件焦點的窗口,然後代表該窗口的UIWindow對象再將它發送給第一響應者進行處理(第一響應者在 “響應者對象和響應者鏈”部分中描述)

應用程序通過觸碰測試(hit-testing)來尋找事件的第一響應者,即通過遞歸調用視圖層次中視圖對象的hitTest:withEvent:方法來確認發生觸摸的子視圖。觸摸對象的整個生命週期都和該視圖互相關聯,即使觸摸動作最終移動到該視圖區域之外也是如此。“事件處理技巧”部分對觸碰測試在編程方面的一些隱含意義進行討論。

UIApplication對象和每個UIWindow對象都在sendEvent:方法(兩個類都聲明瞭這個方法)中派發事件。由於這些方法是事件進入應用程序的通道,所以,您可以從UIApplicationUIWindow派生出子類,重載其sendEvent:方法,實現對事件的監控或執行特殊的事件處理。但是,大多數應用程序都不需要這樣做。

響應者對象和響應者鏈

響應者對象是可以響應事件並對其進行處理的對象。UIResponder是所有響應者對象的基類,它不僅爲事件處理,而且也爲常見的響應者行爲定義編程接口。UIApplicationUIView、和所有從UIView派生出來的UIKit類(包括UIWindow)都直接或間接地繼承自UIResponder類。

第一響應者是應用程序中當前負責接收觸摸事件的響應者對象(通常是一個UIView對象)。UIWindow對象以消息的形式將事件發送給第一響應者,使其有機會首先處理事件。如果第一響應者沒有進行處理,系統就將事件(通過消息)傳遞給響應者鏈中的下一個響應者,看看它是否可以進行處理。

響應者鏈是一系列鏈接在一起的響應者對象,它允許響應者對象將處理事件的責任傳遞給其它更高級別的對象。隨着應用程序尋找能夠處理事件的對象,事件就在響應者鏈中向上傳遞。響應者鏈由一系列“下一個響應者”組成,其順序如下:

  1. 第一響應者將事件傳遞給它的視圖控制器(如果有的話),然後是它的父視圖。

  2. 類似地,視圖層次中的每個後續視圖都首先傳遞給它的視圖控制器(如果有的話),然後是它的父視圖。

  3. 最上層的容器視圖將事件傳遞給UIWindow對象。
  4. UIWindow對象將事件傳遞給UIApplication單件對象。

如果應用程序找不到能夠處理事件的響應者對象,則丟棄該事件。

響應者鏈中的所有響應者對象都可以實現UIResponder的某個事件處理方法,因此也都可以接收事件消息。但是,它們可能不願處理或只是部分處理某些事件。如果是那樣的話,它們可以將事件消息轉送給下一個響應者,方法大致如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [touches anyObject];
    NSUInteger numTaps = [touch tapCount];
    if (numTaps < 2) {
        [self.nextResponder touchesBegan:touches withEvent:event];
   } else {
        [self handleDoubleTap:touch];
   }
}

請注意:如果一個響應者對象將一個多點觸摸序列的初始階段的事件處理消息轉發給下一個響應者(在touchesBegan:withEvent:方法中), 就應該同樣轉發該序列的其它事件處理消息。

動作消息的處理也使用響應者鏈。當用戶對諸如按鍵或分頁控件這樣的UIControl對象進行操作時,控件對象(如果正確配置的話)會向目標對象發送動作消息。但是,如果目標對象被指定爲nil,應用程序就會像處理事件消息那樣,把該動作消息路由給第一響應者。如果第一響應者沒有進行處理,再發送給其下一個響應者,以此類推,將消息沿着響應者鏈向上傳遞。

調整事件的傳遞

UIKit爲應用程序提供了一些簡化事件處理、甚至完全關閉事件流的編程接口。下面對這些方法進行總結:

  • 關閉事件的傳遞。缺省情況下,視圖會接收觸摸事件。但是,您可以將其userInteractionEnabled屬性聲明設置爲NO,關閉事件傳遞的功能。隱藏或透明的視圖也不能接收事件。

  • 在一定的時間內關閉事件的傳遞。應用程序可以調用UIApplicationbeginIgnoringInteractionEvents方法,並在隨後調用endIgnoringInteractionEvents方法來實現這個目的。前一個方法使應用程序完全停止接收觸摸事件消息,第二個方法則重啓消息的接收。某些時候,當您的代碼正在執行動畫時,可能希望關閉事件的傳遞。

  • 打開多點觸摸的傳遞。 缺省情況下,視圖只接收多點觸摸序列的第一個觸摸事件,而忽略所有其它事件。如果您希望視圖處理多點觸摸,就必須使它啓用這個功能。在代碼或Interface Builder的查看器窗口中將視圖的multipleTouchEnabled屬性設置爲YES,就可以實現這個目標。

  • 將事件傳遞限制在某個單獨的視圖上。 缺省情況下,視圖的exclusiveTouch屬性被設置爲NO。將這個屬性設置爲YES會使相應的視圖具有這樣的特性:即當該視圖正在跟蹤觸摸動作時,窗口中的其它視圖無法同時進行跟蹤,它們不能接收到那些觸摸事件。然而,一個標識爲“獨佔觸摸”的視圖不能接收與同一窗口中其它視圖相關聯的觸摸事件。如果一個手指接觸到一個獨佔觸摸的視圖,則僅當該視圖是窗口中唯一一個跟蹤手指的視圖時,觸摸事件纔會被傳遞。如果一個手指接觸到一個非獨佔觸摸的視圖,則僅當窗口中沒有其它獨佔觸摸視圖跟蹤手指時,該觸摸事件纔會被傳遞。

  • 將事件傳遞限制在子視圖上。一個定製的UIView類可以通過重載hitTest:withEvent:方法來將多點觸摸事件的傳遞限制在它的子視圖上。這個技巧的討論請參見“事件處理技巧”部分。

處理多點觸摸事件

爲了處理多點觸摸事件,UIView的定製子類(比較不常見的還有UIApplicationUIWindow的定製子類)必須至少實現一個UIResponder的事件處理方法。本文的下面部分將對這些方法進行描述,討論處理常見手勢的方法,並展示一個處理複雜多點觸摸事件的響應者對象實例,以及就事件處理的某些技術提出建議。

事件處理方法

在一個多點觸摸序列發生的過程中,應用程序會發出一系列事件消息。爲了接收和處理這些消息,響應者對象的類必須至少實現下面這些由UIResponder類聲明的方法之一:

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

在給定的觸摸階段中,如果發生新的觸摸動作或已有的觸摸動作發生變化,應用程序就會發送這些消息:

上面這些方法都和特定的觸摸階段(比如UITouchPhaseBegan)相關聯,該信息存在於UITouch對象的phase屬性聲明中。

每個與事件處理方法相關聯的消息都有兩個參數。第一個參數是一個UITouch對象的集合,表示給定階段中新的或者發生變化的觸摸動作;第二個參數是一個UIEvent對象,表示這個特定的事件。您可以通過這個事件對象得到與之相關聯的所有觸摸對象(allTouches),或者發生在特定的視圖或窗口上的觸摸對象子集。其中的某些觸摸對象表示自上次事件消息以來沒有發生變化,或雖然發生變化但處於不同階段的觸摸動作。

爲了處理給定階段的事件,響應者對象常常從傳入的集合參數中取得一或多個UITouch對象,然後考察這些對象的屬性或取得它們的位置(如果需要處理所有觸摸對象,可以向該NSSet對象發送anyObject消息)。UITouch類中有一個名爲locationInView:的重要方法,如果傳入self參數值,它會給出觸摸動作在響應者座標系統中的位置(假定該響應者是一個UIView對象,且傳入的視圖參數不爲nil)。另外,還有一個與之平行的方法,可以給出觸摸動作之前位置(previousLocationInView:)。UITouch實例的屬性還可以給出發生多少次觸碰(tapCount)、觸摸對象的創建或最後一次變化發生在什麼時間(timestamp)、以及觸摸處於什麼階段(phase)。

響應者類並不是必須實現上面列出的所有三個事件方法。舉例來說,如果它只對手指離開屏幕感興趣,則只需要實現touchesEnded:withEvent:方法就可以了。

在一個多點觸摸序列中,如果響應者在處理事件時創建了某些持久對象,則應該實現touchesCancelled:withEvent:方法,以便當系統取消該序列的時候對其進行清理。多點觸摸序列的取消常常發生在應用程序的事件處理遭到外部事件—比如電話呼入—破壞的時候。請注意,響應者對象同樣應該在收到多點觸摸序列的touchesEnded:withEvent:消息時清理之前創建的對象(“事件處理技巧”部分討論瞭如何確定一個序列中的最後一個touch-up事件)。

處理單個和多個觸碰手勢

iPhone應用程序中一個很常見的手勢是觸擊:即用戶用手指觸碰一個對象。響應者對象可以以一種方式響應單擊,而以另外一種方式響應雙擊,甚至可能以第三種方式響應三次觸擊。您可以通過考察UITouch對象的tapCount屬性聲明值來確定用戶在一個響應者對象上的觸擊次數,

取得這個值的最好地方是touchesBegan:withEvent:touchesEnded:withEvent:方法。在很多情況下,我們更傾向於後者,因爲它與用戶手指離開屏幕的階段相對應。在觸摸結束階段(UITouchPhaseEnded)考察觸擊的次數可以確定手指是真的觸擊,而不是其它動作,比如手指接觸屏幕後拖動的動作。

程序清單3-1展示瞭如何檢測某個視圖上是否發生雙擊。

程序清單3-1  檢測雙擊手勢

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    UITouch       *touch = [touches anyObject];
 
    if ([touch tapCount] == 2) {
        CGPoint tapPoint = [theTouch locationInView:self];
        // Process a double-tap gesture
    }
}

當一個響應者對象希望以不同的方式響應單擊雙擊事件時,就會出現複雜的情況。舉例來說,單擊的結果可能是選定一個對象,而雙擊則可能是顯示一個編輯視圖,用於編輯被雙擊的對象。那麼,響應者對象如何知道一個單擊不是另一個雙擊的起始部分呢?我們接下來解釋響應者對象如何藉助上文剛剛描述的事件處理方法來處理這種情況:

  1. touchesEnded:withEvent:方法中,當觸擊次數爲一時,響應者對象就向自身發送一個performSelector:withObject:afterDelay:消息,其中的選擇器標識由響應者對象實現的、用於處理單擊手勢的方法;第二個參數是一個NSValueNSDictionary對象,用於保存相關的UITouch對象;時延參數則表示單擊和雙擊手勢之間的合理時間間隔。

    請注意:使用一個NSValue對象或字典來保存觸摸對象是因爲它們會保持傳入的對象。然而,您自己在進行事件處理時,不應該對UITouch對象進行保持。

  2. touchesBegan:withEvent:方法中,如果觸擊次數爲二,響應者對象會向自身發送一個cancelPreviousPerformRequestsWithTarget:消息,取消當前被掛起和延期執行的調用。如果觸碰次數不爲二,則在指定的延時之後,先前步驟中由選擇器標識的方法就會被調用,以處理單擊手勢。

  3. touchesEnded:withEvent:方法中,如果觸碰次數爲二,響應者會執行處理雙擊手勢的代碼。

檢測碰擦手勢

水平和垂直的碰擦(Swipe)是簡單的手勢類型,您可以簡單地在自己的代碼中進行跟蹤,並通過它們執行某些動作。爲了檢測碰擦手勢,您需要跟蹤用戶手指在期望的座標軸方向上的運動。碰擦手勢如何形成是由您自己來決定的,也就是說,您需要確定用戶手指移動的距離是否足夠長,移動的軌跡是否足夠直,還有移動的速度是否足夠快。您可以保存初始的觸碰位置,並將它和後續的touch-moved事件報告的位置進行比較,進而做出這些判斷。

程序清單3-2展示了一些基本的跟蹤方法,可以用於檢測某個視圖上發生的水平碰擦。在這個例子中,視圖將觸摸的初始位置存儲在名爲startTouchPosition的成員變量中。隨着用戶手指的移動,清單中的代碼將當前的觸摸位置和起始位置進行比較,確定是否爲碰擦手勢。如果觸摸在垂直方向上移動得太遠,就會被認爲不是碰擦手勢,並以不同的方式進行處理。但是,如果手指繼續在水平方向上移動,代碼就繼續將它作爲碰擦手勢來處理。一旦碰擦手勢在水平方向移動得足夠遠,以至於可以認爲是完整的手勢時,處理例程就會觸發相應的動作。檢測垂直方向上的碰擦手勢可以用類似的代碼,只是把x和y方向的計算互換一下就可以了。

程序清單3-2  在視圖中跟蹤碰擦手勢

#define HORIZ_SWIPE_DRAG_MIN  12
#define VERT_SWIPE_DRAG_MAX    4
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    startTouchPosition = [touch locationInView:self];
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self];
 
    // If the swipe tracks correctly.
    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];
    }
    else
    {
        // Process a non-swipe event.
    }
}

處理複雜的多點觸摸序列

觸擊和碰擦是簡單的手勢。如何處理更爲複雜的多點觸摸序列—實際上是解析應用程序特有的手勢—取決於應用程序希望完成的具體目標。您可以跟蹤所有階段的所有觸摸動作,記錄觸摸對象中發生變化的屬性變量,並正確地改變內部的狀態。

說明如何處理複雜的多點觸摸序列的最好方法是通過實例。程序清單3-3展示一個定製的UIView對象如何通過在屏幕上動畫移動“Welcome”標語牌來響應用戶手指的移動,以及如何通過改變歡迎標語的語言來響應用戶的雙擊手勢(例子中的代碼來自一個名爲MoveMe的示例工程,進一步考察該工程可以更好地理解事件處理的上下文)。

程序清單3-3  處理複雜的多點觸摸序列

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    // Only move the placard view if the touch was in the placard view
    if ([touch view] != placardView) {
        // On double tap outside placard view, update placard's display string
        if ([touch tapCount] == 2) {
            [placardView setupNextDisplayString];
        }
        return;
    }
    // "Pulse" the placard view by scaling up then down
    // Use UIView's built-in animation
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.5];
    CGAffineTransform transform = CGAffineTransformMakeScale(1.2, 1.2);
    placardView.transform = transform;
    [UIView commitAnimations];
 
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.5];
    transform = CGAffineTransformMakeScale(1.1, 1.1);
    placardView.transform = transform;
    [UIView commitAnimations];
 
    // Move the placardView to under the touch
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.25];
    placardView.center = [self convertPoint:[touch locationInView:self] fromView:placardView];
    [UIView commitAnimations];
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
 
    // If the touch was in the placardView, move the placardView to its location
    if ([touch view] == placardView) {
        CGPoint location = [touch locationInView:self];
        location = [self convertPoint:location fromView:placardView];
        placardView.center = location;
        return;
    }
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
 
    // If the touch was in the placardView, bounce it back to the center
    if ([touch view] == placardView) {
        // Disable user interaction so subsequent touches don't interfere with animation
        self.userInteractionEnabled = NO;
        [self animatePlacardViewToCenter];
        return;
    }
}

請注意:對於通過描畫自身的外觀來響應事件的定製視圖,在事件處理方法中通常應該只是設置描畫狀態,而在drawRect:方法中執行所有的描畫操作。如果需要了解更多關於描畫視圖內容的方法,請參見“圖形和描畫”部分。

事件處理技巧

下面是一些事件處理技巧,您可以在自己的代碼中使用。

  • 跟蹤UITouch對象的變化

    在事件處理代碼中,您可以將觸摸狀態的相關位置保存下來,以便在必要時和變化之後的UITouch實例進行比較。作爲例子,假定您希望將每個觸摸對象的最後位置和其初始位置進行比較,則在touchesBegan:withEvent:方法中,您可以通過locationInView:方法得到每個觸摸對象的初始位置,並以UITouch對象的地址作爲鍵,將它們存儲在CFDictionaryRef封裝類型中;然後,在touchesEnded:withEvent:方法中,可以通過傳入UITouch對象的地址取得該對象的初始位置,並將它和當前位置進行比較(您應該使用CFDictionaryRef類型,而不是NSDictionary對象,因爲後者需要對其存儲的項目進行拷貝,而UITouch類並不採納NSCopying協議,該協議在對象拷貝過程中是必須的)。

  • 對子視圖或層上的觸摸動作進行觸碰測試

    定製視圖可以用UIViewhitTest:withEvent:方法或CALayerhitTest:方法來尋找接收觸摸事件的子視圖或層,進而正確地處理事件。下面的例子用於檢測定製視圖的層中的“Info” 圖像是否被觸碰。

    - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
        CGPoint location = [[touches anyObject] locationInView:self];
        CALayer *hitLayer = [[self layer] hitTest:[self convertPoint:location fromView:nil]];
     
        if (hitLayer == infoImage) {
            [self displayInfo];
        }
    }

    如果您有一個攜帶子視圖的定製視圖,就需要明確自己是希望在子視圖的級別上處理觸摸事件,還是在父視圖的級別上進行處理。如果子視圖沒有實現touchesBegan:withEvent:touchesEnded:withEvent:、或者touchesMoved:withEvent:方法,則這些消息就會沿着響應者鏈被傳播到父視圖。然而,由於多次觸碰和多點觸摸事件與發生這些動作所在的子視圖是互相關聯的,所以父視圖不會接收到這些事件。爲了保證能接收到所有的觸摸事件,父視圖必須重載hitTest:withEvent:方法,並在其中返回其本身,而不是它的子視圖。

  • 確定多點觸摸序列中最後一個手指何時離開

    當您希望知道一個多點觸摸序列中的最後一個手指何時從視圖離開時,可以將傳入的集合參數中包含的UITouch對象數量和UIEvent參數對象中與該視圖關聯的觸摸對象數量相比較。請看下面的例子:

    - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
        if ([touches count] == [[event touchesForView:self] count]) {
            // last finger has lifted....
        }
    }

看完文檔我們也許還不能完全掌握ios觸摸事件,下面我們結合一個實例來繼續學習。

新建一個項目SwitchByGesture,添加一個UIViewController命名爲SecondViewController。

在ViewController.m中添加下面代碼:

#pragma mark -

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    gestureStartPoint = [touch locationInView:self.view];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches) {
        CGPoint currentPosition = [touch locationInView:self.view];
        CGFloat deltaX = currentPosition.x - self.gestureStartPoint.x;
        CGFloat deltaY = fabsf(currentPosition.y - self.gestureStartPoint.y);
        if (deltaX <= -25 && deltaY <= 10) {
            SecondViewController *secondView = [[SecondViewController alloc] init];
            secondView.title = @"Second Level";
            [self.navigationController pushViewController:secondView animated:YES];
        }
        
    }
}

我們實現了touchBegan方法,在該方法中我們記住觸摸事件其實座標,在touchEnded方法中我們 獲取手勢結束時的座標,接着判斷是否符合我們的條件(即水平向左滑動且上下滑動不超過10),如果符合切換視圖。

接着同樣我們要在SecondViewController.m中實現上述兩個方法只不過判斷條件不同如下:

#pragma mark -
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    gestureStartPoint = [touch locationInView:self.view];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches) {
        CGPoint currentPosition = [touch locationInView:self.view];
        CGFloat deltaX = currentPosition.x - self.gestureStartPoint.x;
        CGFloat deltaY = fabsf(currentPosition.y - self.gestureStartPoint.y);
        if (deltaX >= 25 && deltaY <= 10) {
            [self.navigationController popViewControllerAnimated:YES];
        }
        
    }
}

即水平向右滑動,切換原來的視圖。編譯運行,如果不出意外的話,會出現下面的效果:

向左滑動

 

向右滑動


好了就寫這麼多,有什麼問題請留言,大家一起學習交流!

說明:轉載請註明出處!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章