轉:http://blog.sina.com.cn/s/blog_8988732e01012eaf.html
iPhoneOS中的觸摸事件基于多點觸摸模型。使用者不是通過滑鼠和鍵盤,而是通過觸摸裝置的螢幕來操作對象、輸入資料、以及訓示自己的意圖。iPhoneOS将一個或多個和螢幕接觸的手指識别為多點觸摸序列的一部分,該序列從第一個手指碰到螢幕開始,直到最後一個手指離開螢幕結束。iPhoneOS通過一個多點觸摸序列來跟蹤與螢幕接觸的手指,記錄每個手指的觸摸特征,包括手指在螢幕上的位置和發生觸摸的時間。應用程式通常将特定組合的觸摸識别為手勢,并以使用者直覺的方式來進行響應,比如對收縮雙指距離的手勢,程式的響應是縮小顯示的内容;對輕拂螢幕的手勢,則響應為滾動顯示内容。
請注意:手指在螢幕上能達到的精度和滑鼠指針有很大的不同。當使用者觸擊螢幕時,接觸區域實際上是橢圓形的,而且比使用者想像的位置更靠下一點。根據觸摸螢幕的手指、手指的尺寸、手指接觸螢幕的力量、手指的方向、以及其它因素的不同,其“接觸部位”的尺寸和形狀也有所不同。底層的多點觸摸系統會分析所有的這些資訊,為您計算出單一的觸點。
很多UIKit類對多點觸摸事件的處理方式不同于它的對象執行個體,特别是像
UIButton
和
UISlider
這樣的
UIControl
的子類。這些子類的對象—被稱為控件對象—隻接收特定類型的手勢,比如觸擊或向特定方向拖拽。控件對象在正确配置之後,會在某種手勢發生後将動作消息發送給目标對象。其它的UIKit類則在其它的上下文中處理手勢,比如
UIScrollView
可以為表格視圖和具有很大内容區域的文本視圖提供滾動行為。
某些應用程式可能不需要直接處理事件,它們可以依賴UIKit類實作的行為。但是,如果您建立了
UIView
的定制子類—這是iPhoneOS系統開發的常見模式—且希望該視圖響應特定的觸摸事件,就需要實作處理該事件所需要的代碼。而且,如果您希望一個UIKit對象以不同的方式響應事件,就必須建立架構類的子類,并重載相應的事件處理方法。
事件和觸摸
在iPhone OS中,觸摸動作是指手指碰到螢幕或在螢幕上移動,它是一個多點觸摸序列的一部分。比如,一個pinch-close手勢就包含兩個觸摸動作:即螢幕上的兩個手指從相反方向靠近對方。一些單指手勢則比較簡單,比如觸擊、輕按兩下、或輕拂(即使用者快速碰擦螢幕)。應用程式也可以識别更為複雜的手勢,舉例來說,如果一個應用程式使用具有轉盤形狀的定制控件,使用者就需要用多個手指來“轉動”轉盤,以便進行某種精調。
事件是當使用者手指觸擊螢幕及在螢幕上移動時,系統不斷發送給應用程式的對象。事件對象為一個多點觸摸序列中所有觸摸動作提供一個快照,其中最重要的是特定視圖中新發生或有變化的觸摸動作。一個多點觸摸序列從第一個手指碰到螢幕開始,其它手指随後也可能觸碰螢幕,所有手指都可能在螢幕上移動。當最後一個手指離開螢幕時,序列就結束了。在觸摸的每個階段,應用程式都會收到事件對象。
觸摸資訊有時間和空間兩方面,時間方面的資訊稱為階段(phrase),表示觸摸是否剛剛開始、是否正在移動或處于靜止狀态,以及何時結束—也就是手指何時從螢幕舉起(參見圖3-1)。觸摸資訊還包括目前在視圖或視窗中的位置資訊,以及之前的位置資訊(如果有的話)。當一個手指接觸螢幕時,觸摸就和某個視窗或視圖關聯在一起,這個關聯在事件的整個生命周期都會得到維護。如果有多個觸摸同時發生,則隻有和同一個視圖相關聯的觸摸會被一起處理。類似地,如果兩個觸摸事件發生的間隔時間很短,也隻有當它們和同一個視圖相關聯時,才會被處理為多觸擊事件。
圖3-1 多點觸摸序列和觸摸階段
在iPhone OS中,一個
UITouch
對象表示一個觸摸,一個
UIEvent
對象表示一個事件。事件對象中包含與目前多點觸摸序列相對應的所有觸摸對象,還可以提供與特定視圖或視窗相關聯的觸摸對象(參見圖3-2)。在一個觸摸序列發生的過程中,對應于特定手指的觸摸對象是持久的,在跟蹤手指運動的過程中,UIKit會對其進行修改。發生改變的觸摸屬性變量有觸摸階段、觸摸在視圖中的位置、發生變化之前的位置、以及時間戳。事件處理代碼通過檢查這些屬性的值來确定如何響應事件。
圖3-2
UIEvent
對象及其
UITouch
對象間的關系
系統可能随時取消多點觸摸序列,進行事件處理的應用程式必須做好正确響應的準備。事件的取消可能是由于重載系統事件引起的,電話呼入就是這樣的例子。
事件的傳遞
系統将事件按照特定的路徑傳遞給可以對其進行處理的對象。如“核心應用程式架構”部分描述的那樣,當使用者觸摸裝置螢幕時,iPhoneOS會将它識别為一組觸摸對象,并将它們封裝在一個
UIEvent
對象中,放入目前應用程式的事件隊列中。事件對象将特定時刻的多點觸摸序列封裝為一些觸摸對象。負責管理應用程式的
UIApplication
單件對象将事件從隊列的頂部取出,然後派發給其它對象進行處理。典型情況下,它會将事件發送給應用程式的鍵盤焦點視窗—即擁有目前使用者事件焦點的視窗,然後代表該視窗的
UIWindow
對象再将它發送給第一響應者進行處理(第一響應者在 “響應者對象和響應者鍊”部分中描述)。
應用程式通過觸碰測試(hit-testing)來尋找事件的第一響應者,即通過遞歸調用視圖層次中視圖對象的
hitTest:withEvent:
方法來确認發生觸摸的子視圖。觸摸對象的整個生命周期都和該視圖互相關聯,即使觸摸動作最終移動到該視圖區域之外也是如此。“事件處理技巧”部分對觸碰測試在程式設計方面的一些隐含意義進行讨論。
UIApplication
對象和每個
UIWindow
對象都在
sendEvent:
方法(兩個類都聲明了這個方法)中派發事件。由于這些方法是事件進入應用程式的通道,是以,您可以從
UIApplication
或
UIWindow
派生出子類,重載
其sendEvent:
方法,實作對事件的監控或執行特殊的事件處理。但是,大多數應用程式都不需要這樣做。
響應者對象和響應者鍊
響應者對象是可以響應事件并對其進行處理的對象。
UIResponder
是所有響應者對象的基類,它不僅為事件處理,而且也為常見的響應者行為定義程式設計接口。
UIApplication
、
UIView
、和所有從
UIView
派生出來的UIKit類(包括
UIWindow
)都直接或間接地繼承自
UIResponder
類。
第一響應者是應用程式中目前負責接收觸摸事件的響應者對象(通常是一個
UIView
對象)。
UIWindow
對象以消息的形式将事件發送給第一響應者,使其有機會首先處理事件。如果第一響應者沒有進行處理,系統就将事件(通過消息)傳遞給響應者鍊中的下一個響應者,看看它是否可以進行處理。
響應者鍊是一系列連結在一起的響應者對象,它允許響應者對象将處理事件的責任傳遞給其它更進階别的對象。随着應用程式尋找能夠處理事件的對象,事件就在響應者鍊中向上傳遞。響應者鍊由一系列“下一個響應者”組成,其順序如下:
- 第一響應者将事件傳遞給它的視圖控制器(如果有的話),然後是它的父視圖。
- 類似地,視圖層次中的每個後續視圖都首先傳遞給它的視圖控制器(如果有的話),然後是它的父視圖。
- 最上層的容器視圖将事件傳遞給
對象。UIWindow
-
對象将事件傳遞給UIWindow
單件對象。UIApplication
如果應用程式找不到能夠處理事件的響應者對象,則丢棄該事件。
響應者鍊中的所有響應者對象都可以實作
UIResponder
的某個事件處理方法,是以也都可以接收事件消息。但是,它們可能不願處理或隻是部分處理某些事件。如果是那樣的話,它們可以将事件消息轉送給下一個響應者,方法大緻如下:
|
|
|
|
|
|
|
|
|
請注意:如果一個響應者對象将一個多點觸摸序列的初始階段的事件處理消息轉發給下一個響應者(在
touchesBegan:withEvent:
方法中),就應該同樣轉發該序列的其它事件處理消息。
動作消息的處理也使用響應者鍊。當使用者對諸如按鍵或分頁控件這樣的
UIControl
對象進行操作時,控件對象(如果正确配置的話)會向目标對象發送動作消息。但是,如果目标對象被指定為
nil
,應用程式就會像處理事件消息那樣,把該動作消息路由給第一響應者。如果第一響應者沒有進行處理,再發送給其下一個響應者,以此類推,将消息沿着響應者鍊向上傳遞。
調整事件的傳遞
UIKit為應用程式提供了一些簡化事件處理、甚至完全關閉事件流的程式設計接口。下面對這些方法進行總結:
- 關閉事件的傳遞。預設情況下,視圖會接收觸摸事件。但是,您可以将其
屬性聲明設定為userInteractionEnabled
,關閉事件傳遞的功能。隐藏或透明的視圖也不能接收事件。NO
- 在一定的時間内關閉事件的傳遞。應用程式可以調用
的UIApplication
方法,并在随後調用beginIgnoringInteractionEvents
方法來實作這個目的。前一個方法使應用程式完全停止接收觸摸事件消息,第二個方法則重新開機消息的接收。某些時候,當您的代碼正在執行動畫時,可能希望關閉事件的傳遞。endIgnoringInteractionEvents
- 打開多點觸摸的傳遞。 預設情況下,視圖隻接收多點觸摸序列的第一個觸摸事件,而忽略所有其它事件。如果您希望視圖處理多點觸摸,就必須使它啟用這個功能。在代碼或InterfaceBuilder的檢視器視窗中将視圖的
屬性設定為multipleTouchEnabled
,就可以實作這個目标。YES
- 将事件傳遞限制在某個單獨的視圖上。 預設情況下,視圖的
屬性被設定為exclusiveTouch
。将這個屬性設定為NO
會使相應的視圖具有這樣的特性:即當該視圖正在跟蹤觸摸動作時,視窗中的其它視圖無法同時進行跟蹤,它們不能接收到那些觸摸事件。然而,一個辨別為“獨占觸摸”的視圖不能接收與同一視窗中其它視圖相關聯的觸摸事件。如果一個手指接觸到一個獨占觸摸的視圖,則僅當該視圖是視窗中唯一一個跟蹤手指的視圖時,觸摸事件才會被傳遞。如果一個手指接觸到一個非獨占觸摸的視圖,則僅當視窗中沒有其它獨占觸摸視圖跟蹤手指時,該觸摸事件才會被傳遞。YES
- 将事件傳遞限制在子視圖上。一個定制的
類可以通過重載UIView
方法來将多點觸摸事件的傳遞限制在它的子視圖上。這個技巧的讨論請參見“事件處理技巧”部分。hitTest:withEvent:
處理多點觸摸事件
為了處理多點觸摸事件,
UIView
的定制子類(比較不常見的還有
UIApplication
或
UIWindow
的定制子類)必須至少實作一個
UIResponder
的事件處理方法。本文的下面部分将對這些方法進行描述,讨論處理常見手勢的方法,并展示一個處理複雜多點觸摸事件的響應者對象執行個體,以及就事件處理的某些技術提出建議。
事件處理方法
在一個多點觸摸序列發生的過程中,應用程式會發出一系列事件消息。為了接收和處理這些消息,響應者對象的類必須至少實作下面這些由
UIResponder
類聲明的方法之一:
|
|
|
|
在給定的觸摸階段中,如果發生新的觸摸動作或已有的觸摸動作發生變化,應用程式就會發送這些消息:
- 當一個或多個手指觸碰螢幕時,發送
消息。touchesBegan:withEvent:
- 當一個或多個手指在螢幕上移動時,發送
消息。touchesMoved:withEvent:
- 當一個或多個手指離開螢幕時,發送
消息。touchesEnded:withEvent:
- 當觸摸序列被諸如電話呼入這樣的系統事件所取消時,發送
消息。touchesCancelled:withEvent:
上面這些方法都和特定的觸摸階段(比如
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 檢測輕按兩下手勢
|
|
|
|
|
|
|
|
當一個響應者對象希望以不同的方式響應單擊和輕按兩下事件時,就會出現複雜的情況。舉例來說,單擊的結果可能是標明一個對象,而輕按兩下則可能是顯示一個編輯視圖,用于編輯被輕按兩下的對象。那麼,響應者對象如何知道一個單擊不是另一個輕按兩下的起始部分呢?我們接下來解釋響應者對象如何借助上文剛剛描述的事件處理方法來處理這種情況:
- 在
方法中,當觸擊次數為一時,響應者對象就向自身發送一個touchesEnded:withEvent:
消息,其中的選擇器辨別由響應者對象實作的、用于處理單擊手勢的方法;第二個參數是一個performSelector:withObject:afterDelay:
或NSValue
對象,用于儲存相關的NSDictionary
UITouch
對象;時延參數則表示單擊和輕按兩下手勢之間的合理時間間隔。
請注意:使用一個
對象或字典來儲存觸摸對象是因為它們會保持傳入的對象。然而,您自己在進行事件處理時,不應該對NSValue
對象進行保持。UITouch
- 在
方法中,如果觸擊次數為二,響應者對象會向自身發送一個touchesBegan:withEvent:
消息,取消目前被挂起和延期執行的調用。如果觸碰次數不為二,則在指定的延時之後,先前步驟中由選擇器辨別的方法就會被調用,以處理單擊手勢。cancelPreviousPerformRequestsWithTarget:
- 在
方法中,如果觸碰次數為二,響應者會執行處理輕按兩下手勢的代碼。touchesEnded:withEvent:
檢測碰擦手勢
水準和垂直的碰擦(Swipe)是簡單的手勢類型,您可以簡單地在自己的代碼中進行跟蹤,并通過它們執行某些動作。為了檢測碰擦手勢,您需要跟蹤使用者手指在期望的坐标軸方向上的運動。碰擦手勢如何形成是由您自己來決定的,也就是說,您需要确定使用者手指移動的距離是否足夠長,移動的軌迹是否足夠直,還有移動的速度是否足夠快。您可以儲存初始的觸碰位置,并将它和後續的touch-moved事件報告的位置進行比較,進而做出這些判斷。
程式清單3-2展示了一些基本的跟蹤方法,可以用于檢測某個視圖上發生的水準碰擦。在這個例子中,視圖将觸摸的初始位置存儲在名為
startTouchPosition
的成員變量中。随着使用者手指的移動,清單中的代碼将目前的觸摸位置和起始位置進行比較,确定是否為碰擦手勢。如果觸摸在垂直方向上移動得太遠,就會被認為不是碰擦手勢,并以不同的方式進行處理。但是,如果手指繼續在水準方向上移動,代碼就繼續将它作為碰擦手勢來處理。一旦碰擦手勢在水準方向移動得足夠遠,以至于可以認為是完整的手勢時,處理例程就會觸發相應的動作。檢測垂直方向上的碰擦手勢可以用類似的代碼,隻是把x和y方向的計算互換一下就可以了。
程式清單3-2 在視圖中跟蹤碰擦手勢
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
處理複雜的多點觸摸序列
觸擊和碰擦是簡單的手勢。如何處理更為複雜的多點觸摸序列—實際上是解析應用程式特有的手勢—取決于應用程式希望完成的具體目标。您可以跟蹤所有階段的所有觸摸動作,記錄觸摸對象中發生變化的屬性變量,并正确地改變内部的狀态。
說明如何處理複雜的多點觸摸序列的最好方法是通過執行個體。程式清單3-3展示一個定制的
UIView
對象如何通過在螢幕上動畫移動“Welcome”智語牌來響應使用者手指的移動,以及如何通過改變歡迎智語的語言來響應使用者的輕按兩下手勢(例子中的代碼來自一個名為MoveMe的示例工程,進一步考察該工程可以更好地了解事件處理的上下文)。
程式清單3-3 處理複雜的多點觸摸序列
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
請注意:對于通過描畫自身的外觀來響應事件的定制視圖,在事件處理方法中通常應該隻是設定描畫狀态,而在
drawRect:
方法中執行所有的描畫操作。如果需要了解更多關于描畫視圖内容的方法,請參見“圖形和描畫”部分。
事件處理技巧
下面是一些事件處理技巧,您可以在自己的代碼中使用。
-
跟蹤UITouch對象的變化
在事件處理代碼中,您可以将觸摸狀态的相關位置儲存下來,以便在必要時和變化之後的
執行個體進行比較。作為例子,假定您希望将每個觸摸對象的最後位置和其初始位置進行比較,則在UITouch
方法中,您可以通過touchesBegan:withEvent:
方法得到每個觸摸對象的初始位置,并以locationInView:
對象的位址作為鍵,将它們存儲在UITouch
封裝類型中;然後,在CFDictionaryRef
方法中,可以通過傳入touchesEnded:withEvent:
對象的位址取得該對象的初始位置,并将它和目前位置進行比較(您應該使用UITouch
類型,而不是CFDictionaryRef
對象,因為後者需要對其存儲的項目進行拷貝,而NSDictionary
類并不采納UITouch
協定,該協定在對象拷貝過程中是必須的)。NSCopying
-
對子視圖或層上的觸摸動作進行觸碰測試
定制視圖可以用
的UIView
方法或hitTest:withEvent:
的CALayer
方法來尋找接收觸摸事件的子視圖或層,進而正确地處理事件。下面的例子用于檢測定制視圖的層中的“Info”圖像是否被觸碰。hitTest:
如果您有一個攜帶子視圖的定制視圖,就需要明确自己是希望在子視圖的級别上處理觸摸事件,還是在父視圖的級别上進行處理。如果子視圖沒有實作- (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中添加下面代碼:
我們實作了touchBegan方法,在該方法中我們記住觸摸事件其實坐标,在touchEnded方法中我們擷取手勢結束時的坐标,接着判斷是否符合我們的條件(即水準向左滑動且上下滑動不超過10),如果符合切換視圖。
接着同樣我們要在SecondViewController.m中實作上述兩個方法隻不過判斷條件不同如下:
即水準向右滑動,切換原來的視圖。編譯運作,如果不出意外的話,會出現下面的效果:
向左滑動
向右滑動
好了就寫這麼多,有什麼問題請留言,大家一起學習交流!