IOS 基礎(chǔ)(一)事件,響應(yīng)鏈機(jī)制分析

@[TOC](IOS 事件,響應(yīng)鏈機(jī)制分析)

1. 事件分發(fā)和響應(yīng)者鏈條

1.1 簡(jiǎn)述

  • 事件分發(fā):自上而下的由UIApplication開(kāi)始,一路往最具體的View查找,直到找到最應(yīng)該處理并且能夠處理事件的那個(gè)控件。
  • 響應(yīng)者鏈條:當(dāng)找到那個(gè)最應(yīng)該處理并且能夠處理事件的那個(gè)控件以后,如果這個(gè)控件確實(shí)處理了這個(gè)事件,那么這個(gè)事件的就到此處理完畢,但是很有可能出現(xiàn)的情況是,雖然這個(gè)控件最應(yīng)該處理,也能夠處理事件,但是它并沒(méi)有處理事件,那么這時(shí)這個(gè)事件就要傳給下一個(gè)響應(yīng)者處理,下一個(gè)響應(yīng)者還不處理,那就再下一個(gè),這個(gè)事件就沿著這條響應(yīng)者鏈條找直到響應(yīng)者確實(shí)處理了這個(gè)事件(并中斷了事件傳遞,就是在touch方法里沒(méi)有調(diào)用下一個(gè)響應(yīng)者的touch方法)為止

事件的分發(fā)路徑和響應(yīng)者鏈條的路徑并不是同一條路從兩頭走的關(guān)系。首先,事件分發(fā)時(shí)候除了最開(kāi)始的UIApplication,一路都是在查找下一個(gè)更應(yīng)該響應(yīng)的UIView,而響應(yīng)者鏈條除了考慮UIView以外還有其他的UIResponder,例如UIViewController;其次,事件分發(fā)會(huì)考慮同級(jí)UIView之間的關(guān)系,就是如果一個(gè)UIView有多個(gè)子View,那么哪個(gè)是更應(yīng)該響應(yīng)的View,而響應(yīng)者鏈條則不會(huì)考慮同級(jí)View之間的關(guān)系,一個(gè)View的下一個(gè)響應(yīng)者并不會(huì)考慮除了自己以為其它適合響應(yīng)的同級(jí)View,而是之間考慮它的父View或者是控制器。

2. 事件分發(fā)

當(dāng)我們手指觸摸屏幕時(shí),系統(tǒng)會(huì)生成UITouch對(duì)象(一個(gè)手指一個(gè)UITouch),然后系統(tǒng)又會(huì)幫我們把所有的UITouch對(duì)象包裝成一個(gè)UIEvent,然后把這個(gè)UIEvent交給UIApplication單例維護(hù)著的一個(gè)事件隊(duì)列,當(dāng)輪到這個(gè)事件處理的時(shí)候,UIApplication首先會(huì)將這個(gè)UIEvent交給KeyWindow,然后KeyWindow再交給它的根控制器的View,然后不斷的遞歸尋找最適合處理這個(gè)事件的View。
這個(gè)遞歸查找的方法叫做hitTest
下面我們嘗試的寫(xiě)一下這個(gè)方法的實(shí)現(xiàn):

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //如果不能處理,返回nil
    if (self.hidden==YES||self.alpha<=0.01||self.userInteractionEnabled == NO) {
        return nil;
    }
    
    //如果不應(yīng)該處理返回nil
    if ([self pointInside:point withEvent:event]==NO) {
        return nil;
    }
    
    //找找看有沒(méi)有更適合處理的子View
    
    //最后添加的(也就是最上面的View)先處理
    NSArray *reverseArray = [[self.subviews reverseObjectEnumerator]allObjects];
    for (UIView *subView  in reverseArray) {
        
        CGPoint subPoint = [self convertPoint:point toView:subView];
        UIView *resultView = [subView hitTest:subPoint withEvent:event];
        
        if (resultView) {
            return resultView;
        }
    }
    
    //遍歷完都沒(méi)有子控件更適合,那么最適合的就是自己
    return self;
}

3. 響應(yīng)者鏈條

當(dāng)找到這個(gè)最適合的控件后,會(huì)從這個(gè)控件開(kāi)始嘗試處理事件并傳遞給下一個(gè)響應(yīng)者(直到某個(gè)響應(yīng)者中斷了傳遞)。
那么,怎么尋找下一個(gè)響應(yīng)者呢?原則如下:

如果這個(gè)view是一個(gè)控制器的View那么這個(gè)View的下一個(gè)響應(yīng)者就是控制器。
如果這個(gè)View不是控制器的View那么下一個(gè)響應(yīng)者就是它的父View。
根控制器的下一個(gè)響應(yīng)者是誰(shuí)UIWindow,再下一個(gè)響應(yīng)者是UIApplication,鏈條結(jié)束。

如果我們把A控制器的View添加到B控制器的View上,那么A控制器會(huì)在響應(yīng)者鏈條上嗎,如果在,它的下一個(gè)響應(yīng)者是誰(shuí)?
如果要把A控制器添加到響應(yīng)者鏈條,就要B控制器add A為子控制器,如果不add的話A控制器不會(huì)在這個(gè)響應(yīng)者鏈條內(nèi),A控制器的下一個(gè)響應(yīng)者為A控制器View的父View。

  1. 響應(yīng)鏈?zhǔn)侨绾喂ぷ?,正確找到應(yīng)該響應(yīng)該事件的響應(yīng)者的?

UIKit使用基于視圖的hit-testing來(lái)確定touch事件發(fā)生的位置。具體解釋就是,UIKit將touch的位置和視圖層級(jí)中的view的邊界進(jìn)行了比較,UIView的方法 hitTest:withEvent: 在視圖層級(jí)中進(jìn)行,尋找包含指定touch的最深子視圖。這個(gè)視圖成為touch事件的第一個(gè)響應(yīng)者。
說(shuō)白了就是,當(dāng)有touch事件來(lái)的時(shí)候,會(huì)從最下面的視圖開(kāi)始執(zhí)行 hitTest:withEvent: ,如果符合成為響應(yīng)者的條件,就會(huì)繼續(xù)遍歷它的 subviews 繼續(xù)執(zhí)行 hitTest:withEvent: ,直到找到最合適的view成為響應(yīng)者。

  1. 符合響應(yīng)者的條件包括:
  1. touch事件的位置在響應(yīng)者區(qū)域內(nèi)
  2. 響應(yīng)者 hidden 屬性不為 YES
  3. 響應(yīng)者 透明度 不是 0
  4. 響應(yīng)者 userInteractionEnabled 不為 NO
  1. 響應(yīng)者鏈有以下特點(diǎn):

1、響應(yīng)者鏈通常是由視圖(UIView)構(gòu)成的;
2、一個(gè)視圖的下一個(gè)響應(yīng)者是它視圖控制器(UIViewController)(如果有的話),然后再轉(zhuǎn)給它的父視圖(Super View);
3、視圖控制器(如果有的話)的下一個(gè)響應(yīng)者為其管理的視圖的父視圖;
4、單例的窗口(UIWindow)的內(nèi)容視圖將指向窗口本身作為它的下一個(gè)響應(yīng)者
需要指出的是,Cocoa Touch應(yīng)用不像Cocoa應(yīng)用,它只有一個(gè)UIWindow對(duì)象,因此整個(gè)響應(yīng)者鏈要簡(jiǎn)單一點(diǎn);

4. 事件傳遞和響應(yīng)原理分析

4.1事件傳遞流程圖

  1. 事件流程圖


    在這里插入圖片描述

IOKit.framework 為系統(tǒng)內(nèi)核的庫(kù)
SpringBoard.app 相當(dāng)于手機(jī)的桌面
Source1 主要接收系統(tǒng)的消息
Source0 - UIApplication - UIWindow

  1. 從UIWindow 開(kāi)始步驟,見(jiàn)下圖
在這里插入圖片描述

比如我們?cè)趕elf.view 上依次添加view1、view2、view3(3個(gè)view是同級(jí)關(guān)系),那么系統(tǒng)用hitTest以及pointInside時(shí)會(huì)先從view3開(kāi)始便利,如果pointInside返回YES就繼續(xù)遍歷view3的subviews(如果view3沒(méi)有子視圖,那么會(huì)返回view3),如果pointInside返回NO就開(kāi)始便利view2。
反序遍歷,最后一個(gè)添加的subview開(kāi)始。也算是一種算法優(yōu)化

4.2 HitTest 、pointInside

當(dāng)我們觸控手機(jī)屏幕時(shí)系統(tǒng)便會(huì)將這一操作封裝成一個(gè)UIEvent放到事件隊(duì)列里面,然后Application從事件隊(duì)列取出這個(gè)事件,接著需要找到去響應(yīng)這個(gè)事件的最佳視圖也就是Responder, 所以開(kāi)始的第一步應(yīng)該是找到Responder, 那么又是如何找到的呢?那就不得不引出UIView的2個(gè)方法:

  • -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    返回視圖層級(jí)中能響應(yīng)觸控點(diǎn)的最深視圖
  • -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    返回視圖是否包含指定的某個(gè)點(diǎn)
  • 通過(guò)簡(jiǎn)單示例代碼來(lái)驗(yàn)證一下
EOCLightGrayView *grayView = [[EOCLightGrayView alloc] initWithFrame:CGRectMake(50.f, 100.f, 260.f, 200.f)];
    
    redView = [[EOCRedView alloc] initWithFrame:CGRectMake(0.f, 0.f, 120.f, 100.f)];
    
    EOCBlueView *blueView = [[EOCBlueView alloc] initWithFrame:CGRectMake(140.f, 100.f, 100.f, 100.f)];
    
    EOCYellowView *yellowView = [[EOCYellowView alloc] initWithFrame:CGRectMake(50.f, 360.f, 200.f, 200.f)];
    
    [self.view addSubview:grayView];
    [grayView addSubview:redView];
    [grayView addSubview:blueView];
    [self.view addSubview:yellowView];
在這里插入圖片描述

測(cè)試結(jié)果如下:


在這里插入圖片描述

我們可以得出結(jié)論:

  1. 點(diǎn)擊red,由于yellow 與 grey 同級(jí),yellow 比 grey 后添加,所以先打印yellow,由于觸摸點(diǎn)不在yellow內(nèi),打印grey,然后遍歷grey,打印他的兩個(gè)subviews

  2. 通過(guò)在HitTest返回nil,pointInside并沒(méi)有執(zhí)行,我們可以得知,pointInside調(diào)用順序你在HitTest之后的。

  3. pointInside 的 參數(shù) :(CGPoint)poinit 的值是以自身為坐標(biāo)系的,判斷點(diǎn)是否view內(nèi)的范圍是以view自身的bounds為范圍,而非frame

  4. 如果在grey的hitTest返回[super hitTest:point event:event],則會(huì)執(zhí)行g(shù)ery.subviews的遍歷(subviews 的 hitTest 與 pointInside),grey 的 pointInside 是判斷觸摸點(diǎn)是否在grey的bounds內(nèi)(不準(zhǔn)確),grey 的 hitTest 是判斷是否需要遍歷他的subviews.

  5. pointInside 只是在執(zhí)行hitTest時(shí),會(huì)在hitTest內(nèi)部調(diào)用的一個(gè)方法

  6. pointInside 只是輔助hitTest的關(guān)系

  7. hitTest是一個(gè)遞歸函數(shù)

  • 還原h(huán)itTest內(nèi)部實(shí)現(xiàn)代碼
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    ///hitTest:判斷pointInside,是不是在view里?是的話,遍歷,不是的話返回nil;假設(shè)我就是點(diǎn)擊灰色的,返回的是自己;
    NSLog(@"%s",__func__);
    
    NSArray *subViews = [[self.subviews reverseObjectEnumerator] allObjects];
    UIView *tmpView = nil;
    for (UIView *view in subViews) {
        
        CGPoint convertedPoint = [self convertPoint:point toView:view];
        if ([view pointInside:convertedPoint withEvent:event]) {
            
            tmpView = view;
            break;
            
        }
        
    }
    
    if (tmpView) {
        return tmpView;
    } else if([self pointInside:point withEvent:event]) {
        
        return self;
    } else {
        
        return nil;
        
    }
    
    
    return [self hitTest:point event:event];  //redView
    
    ///這里是hitTest的邏輯
    
    ///alpha(<=0.01)、userInterActionEnabled(NO)、hidden(YES)  pointInside返回的為NO
    
}

4.3 UIRespond 與 響應(yīng)鏈的組成

在這里插入圖片描述

所有視圖按照樹(shù)狀層次結(jié)構(gòu)組織,每個(gè)view都有自己的superView, 包括vc的self.view:

  1. 當(dāng)一個(gè)view被添加到superView上的時(shí)候, 它的nextResponder就會(huì)被指向所在controller

  2. 當(dāng)vc被初始化的時(shí)候,self.view的nextResponder會(huì)被指向所在的controller
    如果當(dāng)前這個(gè)view是控制器的self.view,那么控制器就是上一個(gè)響應(yīng)者,如果當(dāng)前這個(gè)view不是控制器的view,那么父控件就是上一個(gè)響應(yīng)者)

  3. vc的nextResponder會(huì)被指向self.view的superView

  4. 最頂級(jí)的vc的nextResponder指向UIWindow

  5. UIWindow的nextResponder指向UIApplication
    這就形成了響應(yīng)鏈,通過(guò)UIResponder串連起來(lái)的

touches方法實(shí)際上沒(méi)做什么,UIView繼承了它并重寫(xiě),把事件傳遞給nextResponder,相當(dāng)于[self.nextResponder touchBegan:touches withEvent:event]. 當(dāng)一個(gè)view沒(méi)有重寫(xiě)touch事件,那么這個(gè)事件就會(huì)一直傳遞下去, 直到UIApplication. 如果重寫(xiě)了touch方法,這個(gè)view響應(yīng)了事件,事件就被攔截了, 它的nextResponder不會(huì)收到這個(gè)事件.

  • 響應(yīng)鏈?zhǔn)录鬟f 向上傳遞:
  1. 如果view的控制器存在, 就傳遞給控制器,如果控制器不存在,則將其傳遞給它的父視圖.

  2. 在視圖層次結(jié)構(gòu)的最頂級(jí)視圖,如果不能處理收到的事件/消息,則將事件/消息傳遞給window對(duì)象進(jìn)行處理

  3. 如果window對(duì)象不處理,則將其事件/消息傳遞給UIApplication對(duì)象

  4. 如果UIApplication不處理事件/消息,則將其丟棄

  • 監(jiān)聽(tīng)事件的基本流程:
  1. 當(dāng)應(yīng)用程序啟動(dòng)以后創(chuàng)建UIApplication對(duì)象

  2. 然后啟動(dòng)消息循環(huán)監(jiān)聽(tīng)所有事件

  3. 當(dāng)用戶(hù)觸摸屏幕的時(shí)候,消息循環(huán)監(jiān)聽(tīng)到這個(gè)觸摸事件

  4. 消息循環(huán)首先把監(jiān)聽(tīng)到的觸摸事件傳遞給UIApplication對(duì)象

  5. UIApplication對(duì)象再傳遞給UIWindow對(duì)象

  6. UIWindow對(duì)象再傳遞給UIWindow的根控制器rootViewController

  7. 控制器再傳遞給控制器所管理的view

  8. 控制器所管理的view在其內(nèi)部搜索看本次觸摸的點(diǎn)在哪個(gè)控件的范圍內(nèi)

  9. 找到某個(gè)控件以后,調(diào)用這個(gè)控件的touchBegan方法,再一次向上返回,最終返回給消息循環(huán)

  10. 消息循環(huán)知道哪個(gè)按鈕被點(diǎn)擊后, 在搜索這個(gè)按鈕是否注冊(cè)了對(duì)應(yīng)的事件,如果注冊(cè)了,就調(diào)用這個(gè)事件處理程序.(一般就是執(zhí)行控制器中的事件處理方法)

4.4 手勢(shì)與事件關(guān)系

  1. 手勢(shì) 與 hitTest 的關(guān)系

相同上面的學(xué)習(xí)我們可以推測(cè)出,手勢(shì)的響應(yīng)也得必須經(jīng)過(guò)hitTest先找到視圖才能觸發(fā)(已驗(yàn)證)

  1. 手勢(shì)與 觸摸事件的關(guān)系

touch事件是UIView內(nèi)部的東西,而手勢(shì)疊加上去的觸摸事件

subview會(huì)響應(yīng)superview的手勢(shì), 但是同級(jí)的subview不會(huì)響應(yīng)

  1. 系統(tǒng)如何分辨手勢(shì)種類(lèi)

首先我們想在手勢(shì)中調(diào)用 touches 方法必須要導(dǎo)入

import <UIKit/UIGestureRecognizerSubclass.h>

因?yàn)間esture繼承的是NSObject 而不是 UIRespond

通過(guò)嘗試不調(diào)用 tap手勢(shì) 的touchesBegan ,發(fā)現(xiàn)tap手勢(shì)無(wú)法響應(yīng)
通過(guò)嘗試調(diào)用touchesBegan ,但是不調(diào)用 pan手勢(shì) 的touchesMoved ,發(fā)現(xiàn)pan手勢(shì)無(wú)法響應(yīng)
我們通過(guò)UITouch的實(shí)例,可以看到里面有很多屬性,比如點(diǎn)擊的次數(shù),上次的位置等,結(jié)合這個(gè)屬性系統(tǒng)與touches方法就可以判斷出你使用的是什么手勢(shì)

  1. 手勢(shì)與view的touches事件的關(guān)系

首先通過(guò)觸摸事件,先響應(yīng)touchesBegan 以及 touchesMoved,直到手勢(shì)被識(shí)別出來(lái),調(diào)用touchesCancelled,全權(quán)交給手勢(shì)處理。
但是我們可以改變這種關(guān)系

下面是系統(tǒng)的默認(rèn)設(shè)置
 tapGesture.delaysTouchesBegan = NO;   
///是否延遲view的touch事件識(shí)別;如果延遲了(YES),并且手勢(shì)也識(shí)別到了,touch事件會(huì)被拋棄

 tapGesture.cancelsTouchesInView = YES;   
///識(shí)別手勢(shì)之后,是否取消view的touch事件
// 如果為NO, touchesCancelled 不會(huì)調(diào)用,取而代之的是手勢(shì)結(jié)束后touchesEnd

4.5 手勢(shì)識(shí)別

手勢(shì)識(shí)別器 UIGestureRecognizer

簡(jiǎn)介

UIGestureRecognizer是蘋(píng)果在iOS 3.2之后,推出的手勢(shì)識(shí)別功能。UIGestureRecognizer是一個(gè)抽象類(lèi),將觸摸事件封裝成了手勢(shì)對(duì)象,大大簡(jiǎn)化了開(kāi)發(fā)者的開(kāi)發(fā)難度,同時(shí)也提升了用戶(hù)的交互體驗(yàn)。UIGestureRecognizer有七個(gè)子類(lèi),它們具體實(shí)現(xiàn)了不同手勢(shì)的功能。

在這里插入圖片描述

屬性方法,代理

  • 初始化、添加target、移除target
//初始化方法 且 添加 target的方法
- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action
//單獨(dú)添加target的方法
- (void)addTarget:(id)target action:(SEL)action;
//移除target的方法
- (void)removeTarget:(nullable id)target action:(nullable SEL)action;
  • 屬性和方法
//手勢(shì)的狀態(tài)
@property(nonatomic,readonly) UIGestureRecognizerState state;  
//手勢(shì)代理
@property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate;
//手勢(shì)是否有效  默認(rèn)YES
@property(nonatomic, getter=isEnabled) BOOL enabled; 
//獲取手勢(shì)所在的view
@property(nullable, nonatomic,readonly) UIView *view; 
//取消view上面的touch事件響應(yīng)  default  YES **下面會(huì)詳解該屬性**
@property(nonatomic) BOOL cancelsTouchesInView;       
//延遲touch事件開(kāi)始 default  NO   **下面會(huì)詳解該屬性**
@property(nonatomic) BOOL delaysTouchesBegan;
//延遲touch事件結(jié)束 default  YES  **下面會(huì)詳解該屬性**
@property(nonatomic) BOOL delaysTouchesEnded;
//允許touch的類(lèi)型數(shù)組,**下面會(huì)詳解該屬性**
@property(nonatomic, copy) NSArray<NSNumber *> *allowedTouchTypes 
//允許按壓press的類(lèi)型數(shù)組
@property(nonatomic, copy) NSArray<NSNumber *> *allowedPressTypes 
//是否只允許一種touchType 類(lèi)型,**下面會(huì)詳解該屬性**
@property (nonatomic) BOOL requiresExclusiveTouchType 
//手勢(shì)依賴(lài)(手勢(shì)互斥)方法,**下面會(huì)詳解該方法**
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
//獲取在傳入view的點(diǎn)擊位置的信息方法
- (CGPoint)locationInView:(nullable UIView*)view;                         
//獲取觸摸點(diǎn)數(shù)
@property(nonatomic, readonly) NSUInteger numberOfTouches;    
 //(touchIndex 是第幾個(gè)觸摸點(diǎn))用來(lái)獲取多觸摸點(diǎn)在view上位置信息的方法                                     
- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(nullable UIView*)view; 
// 給手勢(shì)加一個(gè)名字,以方便調(diào)式(iOS11 or later可以用)
@property (nullable, nonatomic, copy) NSString *name API_AVAILABLE(ios(11.0)

先來(lái)說(shuō)說(shuō)requiresExclusiveTouchType這個(gè)屬性
是不是有很多人和我之前一樣,把它理解成了設(shè)置為NO,就可以同時(shí)響應(yīng)幾種手勢(shì)點(diǎn)擊了呢?
這個(gè)屬性的意思:是否同時(shí)只接受一種觸摸類(lèi)型,而不是是否同時(shí)只接受一種手勢(shì)。默認(rèn)是YES。設(shè)置成NO,它會(huì)同時(shí)響應(yīng) allowedTouchTypes 這個(gè)數(shù)組里的所有觸摸類(lèi)型。這個(gè)數(shù)組里面裝的touchType類(lèi)型如下:

//目前touchType有三種
typedef NS_ENUM(NSInteger, UITouchType) {
    UITouchTypeDirect,                       // 手指直接接觸屏幕
    UITouchTypeIndirect,                     // 不是手指直接接觸屏幕(例如:蘋(píng)果TV遙控設(shè)置屏幕上的按鈕)
    UITouchTypeStylus NS_AVAILABLE_IOS(9_1), // 觸控筆接觸屏幕
}

如果把requiresExclusiveTouchType設(shè)置為NO,假設(shè)view上添加了tapGesture手勢(shì),你同時(shí)用手點(diǎn)擊和用觸控筆點(diǎn)擊該view,這個(gè)tapGesture手勢(shì)的方法都會(huì)響應(yīng)。
接下來(lái)說(shuō)說(shuō)cancelsTouchesInView、delaysTouchesBegan、delaysTouchesEnd這三個(gè)屬性。
cancelsTouchesInView 屬性默認(rèn)設(shè)置為YES,如果識(shí)別到了手勢(shì),系統(tǒng)將會(huì)發(fā)送touchesCancelled:withEvent:消息,終止觸摸事件的傳遞。也就是說(shuō)默認(rèn)當(dāng)識(shí)別到手勢(shì)時(shí),touch事件傳遞的方法將被終止,如果設(shè)置為NO,touch事件傳遞的方法仍然會(huì)被執(zhí)行。

delaysTouchesBegan 用于控制事件的開(kāi)始響應(yīng)的時(shí)機(jī),"是否延遲響應(yīng)觸摸事件"。設(shè)置為NO,不會(huì)延遲響應(yīng)觸摸事件,如果我們?cè)O(shè)置為YES,在手勢(shì)沒(méi)有被識(shí)別失敗前,都不會(huì)給事件傳遞鏈發(fā)送消息。

delaysTouchesEnd 用于控制事件結(jié)束響應(yīng)的時(shí)機(jī),"是否延遲結(jié)束觸摸事件",設(shè)置為NO,則會(huì)立馬調(diào)用touchEnd:withEvent這個(gè)方法(如果需要調(diào)用的話)。設(shè)置為YES,會(huì)等待一個(gè)很短的時(shí)間,如果沒(méi)有接收到新的手勢(shì)識(shí)別任務(wù),才會(huì)發(fā)送touchesEnded消息到事件傳遞鏈。

手勢(shì)依賴(lài)方法-requireGestureRecognizerToFail
用法:[A requireGestureRecognizerToFail:B] 當(dāng)A、B兩個(gè)手勢(shì)同時(shí)滿(mǎn)足響應(yīng)手勢(shì)方法的條件時(shí),B優(yōu)先響應(yīng),A不響應(yīng)。如果B不滿(mǎn)足條件,A滿(mǎn)足響應(yīng)手勢(shì)方法的條件,則A響應(yīng)。其實(shí)這就是一個(gè)設(shè)置響應(yīng)手勢(shì)優(yōu)先級(jí)的方法。
如果一個(gè)view上添加了多個(gè)手勢(shì)對(duì)象的,默認(rèn)這些手勢(shì)是互斥的,一個(gè)手勢(shì)觸發(fā)了就會(huì)默認(rèn)屏蔽其他手勢(shì)動(dòng)作。比如,單擊和雙擊手勢(shì)并存時(shí),如果不做處理,它就只能發(fā)送出單擊的消息。為了能夠優(yōu)先識(shí)別雙擊手勢(shì),我們就可以用requireGestureRecognizerToFail:這個(gè)方法設(shè)置優(yōu)先響應(yīng)雙擊手勢(shì)。

  • UIGestureRecognizerDelegate代理方法
//開(kāi)始進(jìn)行手勢(shì)識(shí)別時(shí)調(diào)用的方法,返回NO,則手勢(shì)識(shí)別失敗
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;

//手指觸摸屏幕后回調(diào)的方法,返回NO則手勢(shì)識(shí)別失敗
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldReceiveTouch:(UITouch *)touch;

//是否支持同時(shí)多個(gè)手勢(shì)觸發(fā)
//返回YES,則可以多個(gè)手勢(shì)一起觸發(fā)方法,返回NO則為互斥
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)
otherGestureRecognizer;

//下面這個(gè)兩個(gè)方法也是用來(lái)控制手勢(shì)的互斥執(zhí)行的
//這個(gè)方法返回YES,第二個(gè)手勢(shì)的優(yōu)先級(jí)高于第一個(gè)手勢(shì)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)
otherGestureRecognizer 

//這個(gè)方法返回YES,第一個(gè)手勢(shì)的優(yōu)先級(jí)高于第二個(gè)手勢(shì)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)
otherGestureRecognizer

子類(lèi)

  • 點(diǎn)擊手勢(shì)——UITapGestureRecognizer

  • 捏合手勢(shì)——UIPinchGestureRecognizer

  • 旋轉(zhuǎn)手勢(shì)——UIRotationGestureRecognizer

  • 滑動(dòng)手勢(shì)——UISwipeGestureRecognizer

  • 長(zhǎng)按手勢(shì)——UILongPressGestureRecognizer

  • 平移手勢(shì)——UIPanGestureRecognzer

  • 屏幕邊緣平移手勢(shì)——UIScreenEdgePanGestureRecognzer

參考大神博客:http://www.itdecent.cn/p/4ad8b71246f7

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容