一.事件的類型和傳遞
1.UIEventType(Touches,Motion,RemoteControl),UIEventSubType,
2.touch event的路由規則:touchTest,pointInside:withEvent.
以下是我的一些分析:可能有出入
baseView中連續添加viewA,viewB,viewC,viewA中添viewD,viewD中添加viewE;
先測試hittest:
例子A:viewA,viewB,viewC都實現了 hittest,假設直接返回self,則點擊baseView任何地方,則響應viewC,因為是第一層的最上層,如果viewC的hittest返回nil,則傳遞給viewB,則viewB響應,以此類推。當然你可以再對應方法中調用super hitTest方法,這樣的話就是系統預設的方式去判斷返回的view是哪個,如果不實現pointInView,則是返回點中的view.順便說一句,如果baseView實現hitTest並且返回self,則直接響應baseView了,返回nil,則結束了,不會去自動尋找下層。hitTest是先尋找當前層,再尋找下一層的。此外假設viewA超過了baseView,則點擊baseView外地方,則沒有任何響應。點擊viewA也是。
例子B:viewA,viewB,viewC都實現 hitTest並調用super hitTest或者乾脆就不實現,viewD,viewE實現了pointInside方法,當viewD返回true時,則viewE也會觸發以此pointInside方法,如果返回YES,則viewE觸發touch,返回NO,則viewD觸發touch,如果viewD方法中返回NO,則viewA觸發了touch。
結論1:調用super hitTest其實就是按照事件傳遞的基本規則去尋找事件的響應者,在你自己實現了的方法中不調用super方法,則不會去傳遞到下一層,所以當baseView實現hitTest但是返回nil則沒有調用subview的hittest,但是如果viewC返回nil,則還會調用viewB,因為當baseView沒有實現hitTest方法時,則會預設執行[super hittest].
結論2:pintInside和hitTest傳遞的方式都是從上層傳遞到下層的捕獲方式的傳遞,當pointInside返回yes,則會判斷其子view是否返回yes,如果有,則再繼續尋找,直到都返回no,或者返回yes,並且已經是最後一層,才能確定該view是觸發view.而hitTest則是調用super hitTest方法來觸發這樣的傳遞,如果你返回的view不是你想要的view,還可以決定其他view來作為觸發的view,反正最後關鍵是看方法返回的是哪個UIView作為觸發對象。
3.first responder是指第一個處理應用事件的對象(通常是UIView),這些事件不僅僅是touch事件
a)Motion events
b)remote-control events
c)Action messages:使用者在應用中建立了一個control(button或者slider),並且沒有為其指定target.
d)editing-menu message:
e)text-editing:UIKit會自動將text field或者text view 設定成first responder
二.多觸摸事件
1.UIEvent提供了整個事件程序中的touch事件
a)allTouches
b)touchesForWindow:返回針對某個window的touch對象
c)touchesForView:返回針對某個view的touch對象
2.UITouch某個touch事件
a)anyObject:返回一個touch對象,NSSet是一個無序的集合,他是用hashcode來儲存資料的,不同於NSArray
b)locationInView,previousLocationView:傳遞self的話,則是返回這個view中的位置
c)tapCount:
d)timstamp
3.touch相關的一些方法
a)userInteractionEnabled
b)UIApplication的beginIgnoringInteractionEvents和endIgnoringInteractionEvents
c)multipleTouchEnabled
d)exclusiveTouch
e)hitTest:withEvent來限制多點觸摸事件
4.處理複雜的多觸摸事件
a)multipleTouchEnabled=YES
b)要用Core Foundation Dictionary (CFDictionaryRef)來跟蹤touches以你為UITouch是沒有遵循NSCopying協議,所以不能使用NSDictionary
if ([touches count] > 0) { for (UITouch *touch in touches) { CGPoint *point = (CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch); if (point == NULL) { point = (CGPoint *)malloc(sizeof(CGPoint)); CFDictionarySetValue(touchBeginPoints, touch, point); } *point = [touch locationInView:view.superview]; } }
從touchBeginPoints查看是否有touch為key的value,如果沒有,則建立它,然後在touch上設定該point指標,注意這個是指標,真正賦值的是*point=[touch……],注意給賦值的時候是需要加一個*來賦值。
5.如何判斷多觸摸事件的結束
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { if ([touches count] == [[event touchesForView:self] count]) { // last finger has lifted.... }}
6.轉寄touch events,sendEvent(這個可能需要進一步擷取資料)
7.其他一些注意點有助於處理觸摸事件
a)總是實現event-cancel method,因為你的真箇觸摸事件流程中可能都會遇到cancel的情況,比方說接到一個郵件或者call.你所要做的就是恢複之前的狀態
b)如果你的event方法是是現在一個UIView,UIViewController或者很少見的UIResponder的子類的話,你需要實現所有touch方法,不要調用super方法
如果是其他UIKit responder class,比如UIImageView,UISlider,則不用實現所有touch方法,但是需要調用super對應的方法
c)不要往其它UIKit架構對象轉寄事件,而是只有你自己的custom View of UIView
d)所有事件方法中不要涉及繪製部分,只需要標記狀態,所有繪製代碼應該讓drawRect來協助實現
三.手勢
1.在發送touch事件的結束階段(Ended Phase)到hit-test view前,window對象會將它發送到手勢辨識器所涉及的view及其subviews
2.locationInView:和locationOfTouch:inView
3.處理多個手勢並存的情況
a)接受另外一個手勢前需要另外一個手勢失敗:
比如說單擊手勢是需要雙擊手勢確定失敗後才執行:[singleTap requireGestureRecognizerToFail:doubleTap];
b)通過分析多觸摸touch事件來阻止某些手勢辨識器
UIGestureRecognizerDelegate裡
gestureRecognizerShouldBegin:當一個手勢辨識器準備將狀態標識為UIGestureRecognizerStatePossible前調用,如果返回NO,則標記為失敗
gestureRecognizer:shouldReceiveTouch:這個方法是在window調用touchesBegan事件前調用,返回NO,可以阻止guesture接收這些touch事件
此外還有在UIGestureRecognizerSubclass.h中的2個方法類似
-(BOOL)canPreventGestureRecognizer:(UIGestureRecognizer*)preventedGestureRecognizer;
-(BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer*)preventingGestureRecognizer;
c)允許多個手勢識別
UIGestureRecognizerDelegate裡gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
4.touches事件和手勢事件的流程
假設一個2指的操作:
a)發送2個began
phase touch事件給辨識器,但是辨識器不認,則發送給UIView.
b)發送2個move phase touch事件給辨識器,辨識器依舊無法識別,然後發給UIView.
c)一個手指離開view,發送一個touch事件給ended phase touch事件給辨識器,由於辨識器資料不夠,還是不能確認,不過這個事件被暫時阻止傳送給UIView
d)第二個手指離開,又發送了個touch事件,辨識器識別出了手勢,將狀態標記成UIGuestureRecognizerStateRecognized了,view會接受touchesCancelled:withEvent:
假設最後一步辨識器沒有識別出,則會將2個end phase touch事件發給touchesEnded:withEvent:方法
此外如果是一個連續的手勢識別,則這個狀態可能會被標識為UIGuestureRecognizerStateBegan,然後所有事件都會發送給手勢辨識器,而不發送給view了
影響事件的幾個參數:
cancelsTouchesInView(預設為YES):NO的話,手勢識別失敗的話,也不會調用cancel,那麼之前的touch事件也不會是無效的了
delaysTouchesBegan(預設為NO) 在手勢識別結束前,不會有任何手勢事件發送給view
delaysTouchesEnded(預設為YES)如果是NO,原本view接受的順序是touchesBegan,touchesEnded,touchesBegan,touchesCancelled;YES的話則是touchesBegan,touchesBegan,touchesCancelled,touchesEnded
5.建立自訂手勢識別
手勢狀態的變化(不連續的(如tap),連續的(如pan)),詳細代碼可以看Event
handling Guide或者《iphone4開發基礎》。
四.搖動事件
1.聲明是first Responder
- (BOOL)canBecomeFirstResponder { return YES;} - (void)viewDidAppear:(BOOL)animated { [self becomeFirstResponder];}
2.實現代理方法
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{} - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{ [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.5]; self.view.transform = CGAffineTransformIdentity; for (UIView *subview in self.view.subviews) { subview.transform = CGAffineTransformIdentity; } [UIView commitAnimations]; for (TransformGesture *gesture in [window allTransformGestures]) { [gesture resetTransform]; }} - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{}
3.此外我在開發相關程式時以為這個一定要作為rootcontroller才能響應,其實不是的,假設我有一個rootViewController,然後rootViewController裡顯示了其他controller的View,那麼那個controller同樣可以成為第一響應者,只是如果該controller裡如果不實現motion方法,則會傳遞上去,那麼就會調用rootViewController的motion方法。
4.coreMotion的使用,詳細看文檔或者看《iphone4開發基礎》
大致就是使用陀螺儀,表示方向,加速記表示位移量。該類服務的使用可以分為推送和主動擷取方法。
五.遠端控制多媒體
1.聲明first responder
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; [self becomeFirstResponder];}- (void)viewWillDisappear:(BOOL)animated { [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; [self resignFirstResponder]; [super viewWillDisappear:animated];}
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent { if (receivedEvent.type == UIEventTypeRemoteControl) { switch (receivedEvent.subtype) { case UIEventSubtypeRemoteControlTogglePlayPause: [self playOrStop: nil]; break; case UIEventSubtypeRemoteControlPreviousTrack: [self previousTrack: nil]; break; case UIEventSubtypeRemoteControlNextTrack: [self nextTrack: nil]; break; default: break; } }}