From:http://www.cocoachina.com/bbs/read.php?tid-4380.html
處理多點觸控事件,你自己的定製UIview子類別(或者,
不頻常,你自己的定製UIApplication或UIwindow子類別),要實行至少在其中的UIResponder方法事件處理。以下章節描述這些
方法,討論的方法處理常用手勢,顯示出典型的回應者物件處理一個複雜序列多點觸控事件,並提出了一些技術事件處理。
在這一章節:
事件的處理方法
處理輕拍的手勢
處理重拍的手勢
在處理複雜的多點觸控順序
事件處理技巧
事件的處理方法
在一個多點觸控序列,應用程式指派了一系列的事件訊息。接受和處理這些資訊,回應者物件類別必須實作(implement)至少有下列情形之一的方法(methods)公告
複製代碼
- UIResponder :
- ﹣(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- ﹣(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- ﹣(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
|
應用程式發送這些訊息,針對某個觸摸(touches)階段,當有新的或改變觸摸:
.一個或更多的手指觸摸下在螢幕上,它發送了touchesBegan : withEvent :訊息。
.一個或更多的手指移動,它發送了touchesMoved : withEvent :訊息。
.一個或更多的手指從螢幕上離開,它發送了touchesEnded : withEvent :訊息。
每一個這些方法的,是與觸摸階段(例如UITouchPhaseBegan) ,其中任何UItouch物件,你可以找到評估它階段的屬性。
每
一個訊息,就是引用一個事件的處理方法,傳送兩個參數。第一,是一套UItouch物件所代表的新的或經改變觸摸,涉及為特定階段。第二個參數是一個
UIEvent物件代表這個特別的事件。從事件物件,你可以得到所有接觸物件為事件( allTouches
)觸摸物件的一個子集,過濾為View或Windows 。一些這些觸摸物件代表觸及這並沒有改變,自上次事件資訊的,或已改變,但在不同階段進行。
回
應者物件經常處理的事件,為某一特定階段所獲得一種或一種以上的UItouch物件在傳送一套,然後再評估其屬性或獲得它們的位置。
(如果有任何觸摸物件會做,可採取送NSSet物件的一個anyObject資訊)的一個重要方法是locationInView
:,其中,如果傳送self的參數,產生的位置觸摸在回應者物件座標系統(previousLocationInView:)
。一個平行的方法告訴你,上次的位置觸摸( previousLocationInView: ) 。
UITouch的屬性案例(instance)告訴你有多輕拍(taps)已作出( tapCount
),當觸摸己建立或最後己變化(timestamp)且是否觸摸被強拍(swipe),如果這樣,在哪個方向( info)。
回
應者類別並不實作所有三個這事件的方法上面所列。舉例來說,如果它正在尋找一手指時,他們從螢幕離開,它只需實作touchesEnded :
withEvent : 。事實上,不是執行上述的任何方法,回應者纇別可實作touchesChangedwithEvent
:和監測階段,觸及在那個方法。
如果一個回應者建立持久的物件,而在處理事件程序中多點觸摸序列,它應實作
touchesCanceled處置這些物件時,該系統取消了序列。取消經常發生時,一個外來事件,舉例來說,一通電話打亂目前的應用程式的事件處理。注
意到一回應者物件個也應處置這些同樣的物體,當它接收最touchesEnded : withEvent :留言,為一個多點觸摸序列。
(見"事件處理技術" Event-Handling Techniques),以瞭解如何確定最後的觸摸在一個序列) 。
處理輕拍的手勢
一
件很平常的手勢,在iPhone的應用程式中是輕拍(tap):使用者他或她的手指輕拍(taps)一個物件。回應者物件能回應一個單一的輕拍中的一個方
法,一個雙輕拍(double-tap)在另一個,並可能分三輕拍(tripe-tap),但另一種方式表達。以確定有多少次使用者輕拍回應者物件,你會
得到tapCount屬性值的一個UITouch物件。
最好的地方找到這個值(Value)所採用的方法touchesBegan :
withEvent :和touchesEnded : withEvent
:。在很多情況下,後者的方法是首選,因為它對的回應至於觸摸階段在使用者輕離開拍。由尋找該輕拍的計算(count)輕拍後往上(touch-up)階
段( UITouchPhaseEnded ) ,你確保手指,實在是打字中,比如,在案例,輕拍下(touching
down)然後拖曳(dragging )。
在列表8-1 , touchesEnded : withEvent :方法實作回應雙輕拍(double-tap)手勢縮放的內容顯示在一個scroll View。
列表8-1處理雙雙輕拍(double-tap)手勢
複製代碼
- - (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
- {
- UIScrollView *scrollView = (UIScrollView*)[self superview];
- UITouch *touch = [touches anyObject];
- CGSize size;
- CGPoint point;
- if([touch tapCount] == 2) {
- if(![_viewController _isZoomed]) {
- point = [touch locationInView:self];
- size = [self bounds].size;
- point.x /= size.width;
- point.y /= size.height;
- [_viewController _setZoomed:YES];
- size = [scrollView contentSize];
- point.x *= size.width;
- point.y *= size.height;
- size = [scrollView bounds].size;
- point.x -= size.width / 2;
- point.y -= size.height / 2;
- [scrollView setContentOffset:point animated:NO];
- }
- else
- [_viewController _setZoomed:NO];
- }
- }
|
一
個複雜出現時,回應者物件要處理的單輕拍和雙輕手勢有不同的方式。舉例來說,一個單輕拍可能會選擇物件和一個雙輕拍可能會顯示View為編輯該項目這就是
雙輕拍。又是怎樣回應者物件知道,就是單輕拍,是不是第一次的一個組成部分雙輕裡是如何回應者物件可以處理這種情況使用事件處理方法只是描述:
1.
在touchesEnded
: withEvent :當自輕拍計數是一個,該回應者物件發送itself本身是一個performSelector : withObject :
afterDelay
:訊息。在選擇確定了另一種方法實作,由回應者處理單輕拍手勢;物件為第二個參數是相關UITouch物件;延誤(delay)是了一些合理時間間隔去區
分單輕拍和雙輕手勢。
2.
在touchesBegan : withEvent
:,如果輕拍計數是兩個,回應者物件對取消懸而未決的延遲履行發送本身cancelPreviousPerformRequestsWithtarget
:訊息。如果輕拍計數,是不是兩個,該方法所確定的選擇在1.為單輕拍手勢是引用後拖延。
3.
在touchesEnded : withEvent : ,如果輕拍計數是兩個是兩個,回應者履行必要行動,處理雙輕手勢。
處理重拍的手勢
當
使用者非常迅速的一個或一個以上的手指穿過螢幕時,系統認為這是重拍的手勢。重拍手勢常常被用來滾動的內容或去向前和向後的一個相連的一系列項目。該系統
監視涉及在螢幕上動作,如果用手指的方向和速度都是一致可能是重拍手勢,它合集info屬性的相關UITouch物件;屬性工作表明不僅發生一重拍,但其方
向。由於這種檢測需要跟蹤的觸摸隨著時間的推移,行蹤,可在一個UITouchPhaseMoved階段,並沒有被標示為一重拍,即使後來變成了有標記
的,因為重拍。
註:一重拍手勢,是編程等於一快速輕拍手勢,雖然他們有不同的人機介面的內涵。還能用,快速輕拍是使用轉移頁面之間的內容,如照片。一重拍,是用來改變一些元素;舉例說,重拍在一封電子郵件上,造成刪除按鈕出現。
但
是,該系統可能稍後不標註觸摸作為重拍,如果隨後跟蹤的手指顯示了運動不一致一重拍的手勢。如果一個回應者物件在尋求一個重拍在一個特定方向,並沒有理
會,最後重拍觸摸的狀態,它可以測試info屬性的任何一項UIResponder事件的處理方法。一般來說,你最有興趣,是否觸動作是一重拍,在觸摸物
件的UITouchPhaseEnded階段。你會因此尋找和評估,重拍常數在touchesEnded : withEvent :方法。
列表8-2顯示一個簡單touchesEnded : withEvent :實作是為了尋找重拍(swipes)至左和右,並執行適當的行動方向。
列表8-2處理重拍的手勢
複製代碼
- -- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
- UITouch *touch = [touches anyObject];
- if (touch.info & UITouchInfoSwipedRight) {
- [self showPreviousImage];
- } else if (touch.info & UITouchInfoSwipedLeft) {
- [self showNextImage];
- }
- return;
- }
|
該
方法取得了一個觸摸物件從通過在設定評估bit
mark物件的info屬性,以確定是否UITouchInfoSwipedRight和UITouchInfoSwipedLeft是常量。注意,當該
系統探測到重拍的手勢,在一個方向,如"向右上方的螢幕上" ,它規定了兩個常數bit mark比,以顯示這個方向(例如,
UITouchInfoSwipedRight和UITouchInfoSwipedUp ) 。
在處理複雜的多點觸控順序
輕拍(taps)及重拍(swipes)是簡單的手勢。處理多點觸控摸順序是更為複雜-效果上,詮釋一個特定應用手勢取決於哪些應用程式正試圖完成的任務。你可能要跟蹤所有涉觸模的階段,錄製觸摸屬性已改變,變更內部狀態的。
最好的方法傳達你如何可能處理複雜的多點觸控摸順序,用一個例子。列表8-3日,顯示出如何定製UIView物件回應觸及動態"歡迎"的標語牌圍繞在螢幕上當用手指的動作,當使用者重拍的手勢,並改變歡迎的語言的。
複製代碼
- (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;
- }
- }
|
註:定製客戶化View,重新劃定自己在迴響應事件的處理,一般只應制定一套狀態在事件的處理方法,並履行所有的繪畫在drawrect :方法。要瞭解更多關於提請查看內容,參見" Graphics and Drawing. " 。
程式碼則表8-4說明了兩種不同的技術。首先,它處理所有接觸階段,在touchesChangedWithEvent :方法,取代由個人接觸階段方法。其次,它處理多點接觸,利用每一個接觸去演奏"prong" ,在類比樂器( a kalimba )
列表8-4處理多點觸控在touchesChangedWithEvent:
複製代碼
- - (void)touchesChangedWithEvent:(UIEvent *)event
- {
- // if the instrument body is showing...
- if (self.frontShowing) {
- //iterate through all the touches currently active
- NSSet *touches = [event allTouches];
- for (UITouch *myTouch in touches) {
- CGPoint location = [myTouch locationInView:self];
- //if it's a new touch
- if (myTouch.phase == UITouchPhaseBegan) {
- //test for all kalimba prongs
- NSInteger i;
- for (i = 0; i < NUMBER_OF_PRONGS ; i++) {
- if (CGRectContainsPoint(hitRectPlays, location)) {
- //got one... play the note and make it glow
- [[InstrumentModel sharedInstrumentModel] playNoteForRegion:(i)];
- [[self.glowLayers objectAtIndex:i] setHidden: NO];
- }
- }
- } else if (myTouch.phase == UITouchPhaseMoved) {
- CGPoint oldLocation = [myTouch previousLocationInView:self];
- // if touch moved off a prong, stop glowing
- NSInteger i;
- for (i = 0; i < NUMBER_OF_PRONGS ; i++) {
- if (CGRectContainsPoint(hitRectPlays, oldLocation) &&
- !CGRectContainsPoint(hitRectPlays, location)) {
- [[self.glowLayers objectAtIndex:i] setHidden: YES];
- }
- }
- } else if (myTouch.phase == UITouchPhaseEnded) {
- // if touch ends on a prong, stop glowing
- NSInteger i;
- for (i = 0; i < NUMBER_OF_PRONGS ; i++) {
- if (CGRectContainsPoint(hitRectPlays, location)) {
- [[self.glowLayers objectAtIndex:i] setHidden: YES];
- }
- }
- }
- }
- }
- }
|
註:程式碼用在列表8-3,來自moveme的sample,你可以深入研究,以獲得更深入的瞭解事件的處理情況。
事件處理技術
這裡有一些事件的處理技巧,你可以用在你的程式中。
.跟蹤UITouch物件的變化
在
你的事件處理程式碼,你可以儲存有關接觸位元(bit)狀態,為後來的比較與變化UITouch案例(instance)。作為一個例子,說你要比較的最
後位置,每一個接觸到它原來的位置。在touchesBegan : withEvent
:方法,你還可以得到原來的位置,每一個觸摸,從locationInView屬性和儲存那些在CFDictionary物件使用地址的uitouch物
件作為key。其後,在touchesended :
withevent使用方法:你可以使用的地址,每個通過在uitouch物件,以擷取該物件的原始位置,並比較其目前的位置。
(你應該使用cfdictionary物件,而不是一個nsdictionary物件;後者的複製品,其key,但uitouch纇別並不採用
nscopying議定書,其中規定,為物件拷貝) 。
. Hit-testing就觸摸在subview或Layer
一個
客制化View的可以用hitTest : withEvent :方法UIView或hitTest
:方法CALayer找到subview或層就是接受一個觸摸和妥善的處理處理事件。下面的例子偵測時,
"info"的image在客戶化View我一層,是輕拍。
複製代碼
- - (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];
- }
- }
|
如
果你有一個客制的View與subviews
,你需要來決定是否需要處理涉及在subview等級或superview等級。如果subviews不辦處理實作touchesBegan :
withEvent : , touchesEnded : withEvent : , touchesMoved : withEvent
:,或touchesChangedWithEvent : ,然後這些訊息傳達了回應者鏈向superview
。但是,由於多種輕拍及多重觸及正相關,與subviews那裡,他們第一次發生, superview將不會接受這些觸及。為確保接待,對各種觸及,
superview應淩駕hitTest : withEvent :要恢複本身,而不是它的任何subviews 。
.決定當最後手指在一個多點摸序列已解除
當你想知道當最後手指在一個多點觸摸順序是否己離開View,比較有多少UITouch物件在傳送一套與若干觸模為了View所維繫傳送在UIEvent物件。例如:
複製代碼
- - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
- if ([touches count] == [[event touchesForView:self] count]) {
- // last finger has lifted....
- }
- }