iOS 手势操作和事件传递响应链
iOS 手勢操作和事件傳遞響應鏈
概述
iOS中的事件可以分為3大類型:觸摸事件、加速計事件、遠程控制事件。
在我們點擊屏幕的時候,iphone OS獲取到了用戶進行了“單擊”這一行為,操作系統把包含這些點擊事件的信息包裝成UITouch和UIEvent形式的實例,然后找到當前運行的程序,逐級尋找能夠響應這個事件的對象,直到沒有響應者響應。這一尋找的過程,被稱作事件的響應鏈。
Hit-Test 機制
當用戶觸摸(Touch)屏幕進行交互時,系統首先要找到響應者(Responder)。系統檢測到手指觸摸(Touch)操作時,將Touch 以UIEvent的方式加入UIApplication事件隊列中。UIApplication從事件隊列中取出最新的觸摸事件進行分發傳遞到UIWindow進行處理。UIWindow 會通過hitTest:withEvent:方法尋找觸碰點所在的視圖,這個過程稱之為hit-test view。
hitTest 的順序如下
UIApplication -> UIWindow -> Root View -> ··· -> subview
在頂級視圖(Root View)上調用pointInside:withEvent:方法判斷觸摸點是否在當前視圖內;
如果返回NO,那么hitTest:withEvent:返回nil;
如果返回YES,那么它會向當前視圖的所有子視圖發送hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢。
如果有subview的hitTest:withEvent:返回非空對象則A返回此對象,處理結束(注意這個過程,子視圖也是根據pointInside:withEvent:的返回值來確定是返回空還是當前子視圖對象的。并且這個過程中如果子視圖的hidden=YES、userInteractionEnabled=NO或者alpha小于0.1都會并忽略);
如果所有subview遍歷結束仍然沒有返回非空對象,則hitTest:withEvent:返回self;
系統就是這樣通過hit test找到觸碰到的視圖(Initial View)進行響應。
UIResponder
在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收并處理事件。我們稱之為“響應者對象”,UIApplication、UIViewController、UIView都繼承自UIResponder,因此它們都是響應者對象,都能夠接收并處理事件。
繼承了UIResponder就可以處理事件。UIResponder內部提供了以下方法來處理事件:
//觸摸事件: //一根或者多根手指開始觸摸view,系統會自動調用view的下面方法: - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event//一根或者多根手指在view上移動,系統會自動調用view的下面方法(隨著手指的移動,會持續調用該方法): - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event//一根或者多根手指離開view,系統會自動調用view的下面方法: - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event//觸摸結束前,某個系統事件(例如電話呼入)會打斷觸摸過程,系統會自動調用view的下面方法: - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event - //加速計事件: - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;//遠程控制事件: -(void)remoteControlReceivedWithEvent:(UIEvent *)event;UITouch
使 UIView 跟隨手指移動
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {[super touchesMoved:touches withEvent:event];UITouch *touch = [touches anyObject];CGPoint current = [touch locationInView:moveV];CGPoint previous = [touch previousLocationInView:moveV];CGPoint point = moveV.center;point.x += current.x - previous.x;point.y += current.y - previous.y;NSLog(@"point:%@ current:%@",NSStringFromCGPoint(point),NSStringFromCGPoint(current));moveV.center = point; }當用戶用一根手指觸摸屏幕時,會創建一個與手指相關聯的UITouch對象,一根手指對應一個UITouch對象。
UITouch保存著跟手指相關的信息,比如觸摸的位置、時間、階段:
(1)當手指移動時,系統會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置。
(2)當手指離開屏幕時,系統會銷毀相應的UITouch對象。
UIEvent
每產生一個事件,就會產生一個UIEvent對象,稱為事件對象,記錄事件產生的時刻和類型。
常見屬性:
//事件類型: @property(nonatomic,readonly) UIEventType type; @property(nonatomic,readonly) UIEventSubtype subtype; //事件產生的時間: @property(nonatomic,readonly) NSTimeInterval? timestamp;一次完整的觸摸過程會經歷三個階段:
//觸摸開始: - (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 *)event4個觸摸事件處理方法中,都有NSSet *touches和UIEvent *event兩個參數:
(1)一次完整的觸摸過程中,只會產生一個事件對象,4個觸摸方法都是同一個event參數
(2)如果兩根手指同時觸摸一個view,那么view只會調用一次touchesBegan:withEvent:方法,touches參數中裝著2個UITouch對象
(3)如果這兩根手指一前一后分開觸摸同一個view,那么view會分別調用2次touchesBegan:withEvent:方法,并且每次調用時的touches參數中只包含一個UITouch對象
(4)根據touches中UITouch的個數可以判斷出是單點觸摸還是多點觸摸
事件的產生和傳遞
發生觸摸事件后,系統會將該事件加入到一個由UIApplication管理的事件隊列中,UIApplication會從事件隊列中取出最前面的事件,并將事件分發下去以便處理。通常,先發送事件給應用程序的主窗口(keyWindow),主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,也就是說keyWindow最先收到觸摸事件。這也是整個事件處理過程的第一步,找到合適的視圖控件后,就會調用視圖控件的touches方法來作具體的事件處理:
touchesBegan…
touchesMoved…
touchedEnded…
這些touches方法的默認做法是將事件順著響應者鏈條向上傳遞,將事件交給上一個響應者進行處理。
如果父控件不能接收觸摸事件,那么子控件就不可能接收到觸摸事件
UIView不接收觸摸事件的三種情況:
(1)不接收用戶交互
userInteractionEnabled = NO
(2)隱藏
hidden = YES
(3)透明
alpha = 0.0 ~ 0.01
UIImageView的userInteractionEnabled默認就是NO,因此UIImageView以及它的子控件默認是不能接收觸摸事件的。
響應者鏈條
響應者鏈條示意圖:
響應者鏈的事件傳遞過程:
(1)如果view的控制器存在,就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父視圖。
(2)在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理。
(3)如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象。
(4)如果UIApplication也不能處理該事件或消息,則將其丟棄。
觸摸事件完整處理過程:
(1)先將事件對象由上往下傳遞(由父控件傳遞給子控件),找到最合適的控件來處理事件。
(2)調用最合適控件的touches…方法。
(3)如果這個控件調用了[super touches…];就會將事件順著相應鏈條往下傳遞,傳遞給上一個響應者。
(4)接著就會調用上一個響應者的touches…方法。
(5)事件還可以繼續往上傳遞,直到UIApplication,如果UIApplication也不處理該事件或消息,則將其丟棄。
上一個響應者:
如果當前這個View是控制器的View,那么控制器就是上一個響應者。
如果當前這個View不是控制器的View,那么父控件就是上一個響應者。
UIGestureRecognizer
為了完成手勢識別,必須借助于手勢識別器——UIGestureRecognizer,利用UIGestureRecognizer,能輕松識別用戶在某個view上面做的一些常見手勢,UIGestureRecognizer是一個抽象類,定義了所有手勢的基本行為,使用它的子類才能處理具體的手勢:
//敲擊 UITapGestureRecognizer//捏合,可用于縮放 UIPinchGestureRecognizer//拖拽 UIPanGestureRecognizer//輕掃 UISwipeGestureRecognizer//旋轉 UIRotationGestureRecognizer//長按 UILongPressGestureRecognizer手勢識別器的用法:
// 創建手勢識別器對象 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];// 設置手勢識別器對象的具體屬性 // 連續敲擊2次 tap.numberOfTapsRequired = 2; // 需要2根手指一起敲擊 tap.numberOfTouchesRequired = 2;// 添加手勢識別器到對應的view上 [self.iconView addGestureRecognizer:tap];// 監聽手勢的觸發 [tap addTarget:self action:@selector(tapIconView:)]; typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {// 沒有觸摸事件發生,所有手勢識別的默認狀態UIGestureRecognizerStatePossible,// 一個手勢已經開始但尚未改變或者完成時UIGestureRecognizerStateBegan,// 手勢狀態改變UIGestureRecognizerStateChanged,// 手勢完成UIGestureRecognizerStateEnded,// 手勢取消,恢復至Possible狀態UIGestureRecognizerStateCancelled,// 手勢失敗,恢復至Possible狀態UIGestureRecognizerStateFailed,// 識別到手勢識別UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded };長按手勢
- (void)addLongPressGes {UILongPressGestureRecognizer *longGes = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];[iconImageView addGestureRecognizer:longGes]; } - (void)longPress:(UILongPressGestureRecognizer *)longP {if(longP.state == UIGestureRecognizerStateBegan){NSLog(@"開始長按");}else if(longP.state == UIGestureRecognizerStateChanged){NSLog(@"長按時手指移動");}else if(longP.state == UIGestureRecognizerStateEnded){NSLog(@"手指離開屏幕");} }清掃手勢
- (void)addSwipGes {UISwipeGestureRecognizer *swip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipGes:)];// 清掃方向swip.direction = UISwipeGestureRecognizerDirectionLeft;[iconImageView addGestureRecognizer:swip]; } - (void)swipGes:(UISwipeGestureRecognizer *)swipe {// 判斷的輕掃的方向if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) {NSLog(@"向左輕掃");}else if(swipe.direction == UISwipeGestureRecognizerDirectionUp){NSLog(@"向上輕掃");} }拖動手勢
- (void)addPanGes {UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];[iconImageView addGestureRecognizer:pan]; } - (void)pan:(UIPanGestureRecognizer *)pan {// 拖動手勢也有狀態if(pan.state == UIGestureRecognizerStateBegan){// 開始拖動}else if(pan.state == UIGestureRecognizerStateChanged){// 獲取當前手指移動的距離,是相對于最原始的點CGPoint transP = [pan translationInView:iconImageView];// 清空上一次的形變iconImageView.transform = CGAffineTransformMakeTranslation(transP.x,transP.y);iconImageView.transform = CGAffineTransformTranslate(iconImageView.transform, transP.x, transP.y);// 復位,讓它相對于上一次. // [pan setTranslation:CGPointZero inView:iconImageView];}else if(pan.state == UIGestureRecognizerStateEnded){// 結束拖動} }捏合手勢
- (void)addPinchGes {UIPinchGestureRecognizer *pin = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pin:)];[iconImageView addGestureRecognizer:pin]; } - (void)pin:(UIPinchGestureRecognizer *)pin {iconImageView.transform = CGAffineTransformScale(iconImageView.transform, pin.scale, pin.scale);// 復位[pin setScale:1.0]; }旋轉手勢
- (void)addRotaGes {UIRotationGestureRecognizer *ro = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(retation:)];// 設置代理可以使其同時支持多個手勢ro.delegate = self;[iconImageView addGestureRecognizer:ro]; } - (void)retation:(UIRotationGestureRecognizer *)rotation {iconImageView.transform = CGAffineTransformRotate(iconImageView.transform, rotation.rotation);[rotation setRotation:0.0]; }演示代碼 XWTouchResponderDemo
參考:
* https://www.jianshu.com/p/ef33cc31a614
總結
以上是生活随笔為你收集整理的iOS 手势操作和事件传递响应链的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 彻底搞懂系统调用
- 下一篇: 二十世纪最伟大的算法,你了解哪个?