天天看點

IOS事件學習總結iOS事件機制(一)iOS事件分發機制(一) hit-Testingios開發 在UiView中添加點選事件UITextView 響應 鍵盤的return(完成鍵)

iOS事件機制(一)

http://www.cnblogs.com/zhw511006/p/3517248.html

本篇内容将圍繞iOS中事件及其傳遞機制進行學習和分析。在iOS中,事件分為三類:

觸控事件(單點、多點觸控以及各種手勢操作)

傳感器事件(重力、加速度傳感器等)

遠端控制事件(遠端遙控iOS裝置多媒體播放等)

這三類事件共同構成了iOS裝置豐富的操作方式和使用體驗,本次就首先來針對第一類事件:觸控事件,進行學習和分析。

Gesture Recognizers

Gesture Recognizers是一類手勢識别器對象,它可以附屬在你指定的View上,并且為其設定指定的手勢操作,例如是點選、滑動或者是拖拽。當觸控事件 發生時,設定了Gesture Recognizers的View會先通過識别器去攔截觸控事件,如果該觸控事件是事先為View設定的觸控監聽事件,那麼Gesture Recognizers将會發送動作消息給目标處理對象,目标處理對象則對這次觸控事件進行處理,先看看如下流程圖。

在iOS中,View就是我們在螢幕上看到的各種UI控件,當一個觸控事件發生時,Gesture Recognizers會先擷取到指定的事件,然後發送動作消息(action message)給目标對象(target),目标對象就是ViewController,在ViewController中通過事件方法完成對該事件的處理。Gesture Recognizers能設定諸如單擊、滑動、拖拽等事件,通過Action-Target這種設計模式,好處是能動态為View添加各種事件監聽,而不用去實作一個View的子類去完成這些功能。

以上過程就是我們在開發中在方法中常見的設定action和設定target,例如為UIButton設定監聽事件等。

常用手勢識别類

在UIKit架構中,系統為我們事先定義好了一些常用的手勢識别器,包括點選、雙指縮放、拖拽、滑動、旋轉以及長按。通過這些手勢識别器我們可以構造豐富的操作方式。

在上表中可以看到,UIKit架構中已經提供了諸如UITapGestureRecognizer在内的六種手勢識别器,如果你需要實作自定義的手勢識别器,也可以通過繼承UIGestureRecognizer類并重寫其中的方法來完成,這裡我們就不詳細讨論了。

每一個Gesture Recognizer關聯一個View,但是一個View可以關聯多個Gesture Recognizer,因為一個View可能還能響應多種觸控操作方式。當一個觸控事件發生時,Gesture Recognizer接收一個動作消息要先于View本身,結果就是Gesture Recognizer作為View處理觸控事件的代表,或者叫代理。當Gesture Recognizer接收到指定的事件時,它就會發送一條動作消息(action message)給ViewController并處理。

連續和不連續動作

觸控動作同時分為連續動作(continuous)和不連續動作(discrete),連續動作例如滑動和拖拽,它會持續一小段時間,而不連續動作例如單擊,它瞬間就會完成,在這兩類事件的處理上又稍有不同。對于不連續動作,Gesture Recognizer隻會給ViewContoller發送一個單一的動作消息(action message),而對于連續動作,Gesture Recognizer會發送多條動作消息給ViewController,直到所有的事件都結束。

為一個View添加GestureRecognizer有兩種方式,一種是通過InterfaceBuilder實作,另一種就是通過代碼實作,我們看看通過代碼來如何實作。

MyViewContoller.m

- (void)viewDidLoad {

     [super viewDidLoad];

     // 建立并初始化手勢對象

     UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]

          initWithTarget:self action:@selector(respondToTapGesture:)];

     // 指定操作為單擊一次

     tapRecognizer.numberOfTapsRequired = 1;

     // 為目前View添加GestureRecognizer

     [self.view addGestureRecognizer:tapRecognizer];

     // ...

}

通過上述代碼,我們實作了為目前MyViewController的View添加一個單擊事件,首先構造了UITapGestureRecognizer對象,指定了target為目前ViewController本身,action就是後面自己實作的處理方法,這裡就呼應了前文提到的Action-Target模式。

在事件處理過程中,這兩種方式所處的狀态又各有不同,首先,所有的觸控事件最開始都是處于可用狀态(Possible),對應UIKit裡面的UIGestureRecognizerStatePossible類,如果是不連續動作事件,則狀态隻會從Possible轉變為已識别狀态(Recognized,UIGestureRecognizerStateRecognized)或者是失敗狀态(Failed,UIGestureRecognizerStateFailed)。例如一次成功的單擊動作,就對應了Possible-Recognized這個過程。

如果是連續動作事件,如果事件沒有失敗并且連續動作的第一個動作被成功識别(Recognized),則從Possible狀态轉移到Began(UIGestureRecognizerStateBegan)狀态,這裡表示連續動作的開始,接着會轉變為Changed(UIGestureRecognizerStateChanged)狀态,在這個狀态下會不斷循環的處理連續動作,直到動作執行完成變轉變為Recognized已識别狀态,最終該動作會處于完成狀态(UIGestureRecognizerStateEnded),另外,連續動作事件的處理狀态會從Changed狀态轉變為Canceled(UIGestureRecognizerStateCancelled)狀态,原因是識别器認為目前的動作已經不比對當初對事件的設定了。每個動作狀态的變化,Gesture Recognizer都會發送消息(action message)給Target,也就是ViewController,它可以根據這些動作消息進行相應的處理。例如一次成功的滑動手勢動作就包括按下、移動、擡起的過程,分别對應了Possible-Began-Changed-Recognized這個過程。

UITouch & UIEvent

在螢幕上的每一次動作事件都是一次Touch,在iOS中用UITouch對象表示每一次的觸控,多個Touch組成一次Event,用UIEvent來表示一次事件對象。

在上述過程中,完成了一次雙指縮放的事件動作,每一次手指狀态的變化都對應事件動作處理過程中得一個階段。通過Began-Moved-Ended這幾個階段的動作(Touch)共同構成了一次事件(Event)。在事件響應對象UIResponder中有對應的方法來分别處理這幾個階段的事件。

touchesBegan:withEvent:

touchesMoved:withEvent:

touchesEnded:withEvent:

touchesCancelled:withEvent:

後面的參數分别對應UITouchPhaseBegan、UITouchPhaseMoved、UITouchPhaseEnded、UITouchPhaseCancelled這幾個類。用來表示不同階段的狀态。

事件傳遞

如上圖,iOS中事件傳遞首先從App(UIApplication)開始,接着傳遞到Window(UIWindow),在接着往下傳遞到View之前,Window會将事件交給GestureRecognizer,如果在此期間,GestureRecognizer識别了傳遞過來的事件,則該事件将不會繼續傳遞到View去,而是像我們之前說的那樣交給Target(ViewController)進行處理。

響應者鍊(Responder Chain)

通常,一個iOS應用中,在一塊螢幕上通常有很多的UI控件,也就是有很多的View,那麼當一個事件發生時,如何來确定是哪個View響應了這個事件呢,接下來我們就一起來看看。

尋找hit-test view

什麼是hit-test view呢?簡單來說就是你觸發事件所在的那個View,尋找hit-test view的過程就叫做Hit-Testing。那麼,系統是如何來執行Hit-Testing呢,首先假設現在有如下這麼一個UI布局,一種有ABCDE五個View。

假設一個單擊事件發生在了View D裡面,系統首先會從最頂層的View A開始尋找,發現事件是在View A或者其子類裡面,那麼接着從B和C找,發現事件是在C或者其子類裡面,那麼接着到C裡面找,這時發現事件是在D裡面,并且D已經沒有子類了,那麼hit-test view就是View D啦。

響應者對象(Responsder Object)

響應者對象是能夠響應并且處理事件的對象,UIResponder是所有響應者對象的父類,包括UIApplication、UIView和UIViewController都是UIResponder的子類。也就意味着所有的View和ViewController都是響應者對象。

第一響應者(First Responder)

第一響應者是第一個接收事件的View對象,我們在Xcode的Interface Builder畫視圖時,可以看到視圖結構中就有First Responder。

這裡的First Responder就是UIApplication了。另外,我們可以控制一個View讓其成為First Responder,通過實作 canBecomeFirstResponder方法并傳回YES可以使目前View成為第一響應者,或者調用View的becomeFirstResponder方法也可以,例如當UITextField調用該方法時會彈出鍵盤進行輸入,此時輸入框控件就是第一響應者。

事件傳遞機制

如上所說,,如果hit-test view不能處理目前事件,那麼事件将會沿着響應者鍊(Responder Chain)進行傳遞,知道遇到能處理該事件的響應者(Responsder Object)。通過下圖,我們來看看兩種不同情況下得事件傳遞機制。

左邊的情況,接收事件的initial view如果不能處理該事件并且她不是頂層的View,則事件會往它的父View進行傳遞。initial view的父View擷取事件後如果仍不能處理,則繼續往上傳遞,循環這個過程。如果頂層的View還是不能處理這個事件的話,則會将事件傳遞給它們的ViewController,如果ViewController也不能處理,則傳遞給Window(UIWindow),此時Window不能處理的話就将事件傳遞給Application(UIApplication),最後如果連Application也不能處理,則廢棄該事件。

右邊圖的流程唯一不同就在于,如果目前的ViewController是由層級關系的,那麼當子ViewController不能處理事件時,它會将事件繼續往上傳遞,直到傳遞到其Root ViewController,後面的流程就跟之前分析的一樣了。

這就是事件響應者鍊的傳遞機制,通過這些内容,我們可以更深入的了解事件在iOS中得傳遞機制,對我們在實際開發中更好的了解事件操作的原理有很大的幫助,也對我們實作複雜布局進行事件處理時增添了多一份的了解。

總結

通過前面的内容分析,我們已經學習并了解了如下内容:

Gesture Recognizers,是用來控制手勢識别的過程和方法,并且其通過Action-Target模式與ViewController的通信的方式。連續和不連續手勢動作情況下GestureRecognizer的狀态轉變。

UITouch和UIEvent對象,他們都是UIKit中來進行事件處理的對象,多個UITouch對象構成一個UIEvent對象,重寫相應的方法可以控制和處理事件各個階段的操作。

系尋找hit-test view的方式、事件傳遞機、制響應者鍊

後記:本篇是iOS事件傳遞機制的上篇,下篇将繼續讨論多點觸控事件和手勢操作的内容!

========

iOS事件分發機制(一) hit-Testing

http://ios.jobbole.com/81864/

iOS中的事件大概分為三種,分别是 Milti-Touch Events, Motion Events 和Remote Control Events(events for controlling multimedia)。

本文将主要針對TouchEvents的分發,做一個詳細的介紹。先抛出一個問題,文章的後續部分會對問題進行解答:iOS7原生的自帶NavigationController可以實作從最左側拖動PopViewController(大約13pt),不管目前可見的ViewController有沒有其他的滑動手勢或者事件,這是為什麼?如何實作。

我們已經處理過太多觸摸事件了,比如按鈕的點選事件,一些View的手勢等等。那到底我們點一下螢幕,目前的View是如何知道他被點選了呢,這個就要通過HitTest來确定了

每當我們點選了一下iOS裝置的螢幕,UIKit就會生成一個事件對象UIEvent,然後會把這個Event分發給目前active的app(官方原文說:Then it places the event object in the active app’s event queue.)

告知目前活動的app有事件之後,UIApplication 單例就會從事件隊列中去取最新的事件,然後分發給能夠處理該事件的對象。UIApplication 擷取到Event之後,Application就糾結于到底要把這個事件傳遞給誰,這時候就要依靠HitTest來決定了。

iOS中,hit-Testing的作用就是找出這個觸摸點下面的View是什麼,HitTest會檢測這個點選的點是不是發生在這個View上,如果是的話,就會去周遊這個View的subviews,直到找到最小的能夠處理事件的view,如果整了一圈沒找到能夠處理的view,則傳回自身。來一個簡單的圖說明一下

假設我們現在點選到了圖中的E,hit-testing将進行如下步驟的檢測(不包含重寫hit-test并且傳回非預設View的情況)

1、觸摸點在ViewA内,是以檢查ViewA的Subview B、C

2、觸摸點不在ViewB内,觸摸點在ViewC内部,是以檢查ViewC的Subview D、E

3、觸摸點不在ViewD内,觸摸點發生在ViewE内部,并且ViewE沒有subview,是以ViewE屬于ViewA中包含這個點的最小機關,是以ViewE變成了該次觸摸事件的hit-TestView

PS.

1、預設的hit-testing順序是按照UIView中Subviews的逆順序

2、如果View的同級别Subview中有重疊的部分,則優先檢查頂部的Subview,如果頂部的Subview傳回nil, 再檢查底部的Subview

3、Hit-Test也是比較聰明的,檢測過程中有這麼一點,就是說如果點選沒有發生在某View中,那麼該事件就不可能發生在View的Subview中,是以檢測過程中發現該事件不在ViewB内,也直接就不會檢測在不在ViewF内。也就是說,如果你的Subview設定了clipsToBounds=NO,實際顯示區域可能超出了superView的frame,你點選超出的部分,是不會處理你的事件的,就是這麼任性!

Hit-Test的檢查機制如上所示,當确定了Hit-TestView時,如果目前的application沒有忽略觸摸事件 (UIApplication:isIgnoringInteractionEvents),則application就會去分發事件(sendEvent:->keywindow:sendEvent:)

UIView中提供兩個方法用來确定hit-testing View,如下所示 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds

當一個View收到hitTest消息時,會調用自己的pointInside:withEvent:方法,如果pointInside傳回YES,則表明觸摸事件發生在我自己内部,則會周遊自己的所有Subview去尋找最小機關(沒有任何子view)的UIView,如果目前View.userInteractionEnabled = NO,enabled=NO(UIControl),或者alpha<=0.01, hidden等情況的時候,hitTest就不會調用自己的pointInside了,直接傳回nil,然後系統就回去周遊兄弟節點。簡而言之,可以寫成這樣

Objective-C

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    if (self.alpha &lt;= 0.01 || !self.userInteractionEnabled || self.hidden) {

        return nil;

    }

    BOOL inside = [self pointInside:point withEvent:event];

    UIView *hitView = nil;

    if (inside) {

        NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];

        for (UIView *subview in enumerator) {

            hitView = [subview hitTest:point withEvent:event];

            if (hitView) {

                break;

            }

        }

        if (!hitView) {

            hitView = self;

        }

        return hitView;

    } else {

        return nil;

    }

}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    if (self.alpha &lt;= 0.01 || !self.userInteractionEnabled || self.hidden) {

        return nil;

    }

    BOOL inside = [self pointInside:point withEvent:event];

    UIView *hitView = nil;

    if (inside) {

        NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];

        for (UIView *subview in enumerator) {

            hitView = [subview hitTest:point withEvent:event];

            if (hitView) {

                break;

            }

        }

        if (!hitView) {

            hitView = self;

        }

        return hitView;

    } else {

        return nil;

    }

}

hit-Test 是事件分發的第一步,就算你的app忽略了事件,也會發生hit-Test。确定了hit-TestView之後,才會開始進行下一步的事件分發。

我們可以利用hit-Test做一些事情,比如我們點選了ViewA,我們想讓ViewB響應,這個時候,我們隻需要重寫View’s hitTest方法,傳回ViewB就可以了,雖然可能用不到,但是偶爾還是會用到的。大概代碼如下:

Objective-C

@interface STPView : UIView

@end

@implementation STPView

- (instancetype)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];

    if (self) {

        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

        button.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);

        button.tag = 10001;

        button.backgroundColor = [UIColor grayColor];

        [button setTitle:@&quot;Button1&quot; forState:UIControlStateNormal];

        [self addSubview:button];

        [button addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];

        UIButton *button2 = [UIButton buttonWithType:UIButtonTypeCustom];

        button2.frame = CGRectMake(0, CGRectGetHeight(frame) / 2, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);

        button2.tag = 10002;

        button2.backgroundColor = [UIColor darkGrayColor];

        [button2 setTitle:@&quot;Button2&quot; forState:UIControlStateNormal];

        [self addSubview:button2];

        [button2 addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];

    }

    return self;

}

- (void)_buttonActionFired:(UIButton *)button {

    NSLog(@&quot;=====Button Titled %@ ActionFired &quot;, [button titleForState:UIControlStateNormal]);

}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    UIView *hitView = [super hitTest:point withEvent:event];

    if (hitView == [self viewWithTag:10001]) {

        return [self viewWithTag:10002];

    }

    return hitView;

}

@end

@interface STPView : UIView

@end

@implementation STPView

- (instancetype)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];

    if (self) {

        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

        button.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);

        button.tag = 10001;

        button.backgroundColor = [UIColor grayColor];

        [button setTitle:@&quot;Button1&quot; forState:UIControlStateNormal];

        [self addSubview:button];

        [button addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];

        UIButton *button2 = [UIButton buttonWithType:UIButtonTypeCustom];

        button2.frame = CGRectMake(0, CGRectGetHeight(frame) / 2, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2);

        button2.tag = 10002;

        button2.backgroundColor = [UIColor darkGrayColor];

        [button2 setTitle:@&quot;Button2&quot; forState:UIControlStateNormal];

        [self addSubview:button2];

        [button2 addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown];

    }

    return self;

}

- (void)_buttonActionFired:(UIButton *)button {

    NSLog(@&quot;=====Button Titled %@ ActionFired &quot;, [button titleForState:UIControlStateNormal]);

}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    UIView *hitView = [super hitTest:point withEvent:event];

    if (hitView == [self viewWithTag:10001]) {

        return [self viewWithTag:10002];

    }

    return hitView;

}

@end

大家可以試一試,上述代碼在點選上面的按鈕的時候,實際會觸發下面按鈕的事件,不是經常用到,但是也算是漲姿勢了,這裡給大家提供一個Category,來自STKit,這個category的目的就是友善的編寫hitTest方法,由于hitTest方法是override,而不是delegate,是以使用預設的實作方式就比較麻煩。Category如下

Objective-C

typedef UIView * (^STHitTestViewBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);

typedef BOOL (^STPointInsideBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);

@interface UIView (STHitTest)

/// althought this is strong ,but i deal it with copy

@property(nonatomic, strong) STHitTestViewBlock hitTestBlock;

@property(nonatomic, strong) STPointInsideBlock pointInsideBlock;

@end

typedef UIView * (^STHitTestViewBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);

typedef BOOL (^STPointInsideBlock)(CGPoint point, UIEvent *event, BOOL *returnSuper);

@interface UIView (STHitTest)

/// althought this is strong ,but i deal it with copy

@property(nonatomic, strong) STHitTestViewBlock hitTestBlock;

@property(nonatomic, strong) STPointInsideBlock pointInsideBlock;

@end

Objective-C

@implementation UIView (STHitTest)

const static NSString *STHitTestViewBlockKey = @&quot;STHitTestViewBlockKey&quot;;

const static NSString *STPointInsideBlockKey = @&quot;STPointInsideBlockKey&quot;;

+ (void)load {

    method_exchangeImplementations(class_getInstanceMethod(self, @selector(hitTest:withEvent:)),

                                   class_getInstanceMethod(self, @selector(st_hitTest:withEvent:)));

    method_exchangeImplementations(class_getInstanceMethod(self, @selector(pointInside:withEvent:)),

                                   class_getInstanceMethod(self, @selector(st_pointInside:withEvent:)));

}

- (UIView *)st_hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];

    UIView *superView = self.superview;

    while (superView) {

        [spaces appendString:@&quot;----&quot;];

        superView = superView.superview;

    }

    NSLog(@&quot;%@%@:[hitTest:withEvent:]&quot;, spaces, NSStringFromClass(self.class));

    UIView *deliveredView = nil;

    // 如果有hitTestBlock的實作,則調用block

    if (self.hitTestBlock) {

        BOOL returnSuper = NO;

        deliveredView = self.hitTestBlock(point, event, &amp;returnSuper);

        if (returnSuper) {

            deliveredView = [self st_hitTest:point withEvent:event];

        }

    } else {

        deliveredView = [self st_hitTest:point withEvent:event];

    }

//    NSLog(@&quot;%@%@:[hitTest:withEvent:] Result:%@&quot;, spaces, NSStringFromClass(self.class), NSStringFromClass(deliveredView.class));

    return deliveredView;

}

- (BOOL)st_pointInside:(CGPoint)point withEvent:(UIEvent *)event {

    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];

    UIView *superView = self.superview;

    while (superView) {

        [spaces appendString:@&quot;----&quot;];

        superView = superView.superview;

    }

    NSLog(@&quot;%@%@:[pointInside:withEvent:]&quot;, spaces, NSStringFromClass(self.class));

    BOOL pointInside = NO;

    if (self.pointInsideBlock) {

        BOOL returnSuper = NO;

        pointInside =  self.pointInsideBlock(point, event, &amp;returnSuper);

        if (returnSuper) {

            pointInside = [self st_pointInside:point withEvent:event];

        }

    } else {

        pointInside = [self st_pointInside:point withEvent:event];

    }

    return pointInside;

}

- (void)setHitTestBlock:(STHitTestViewBlock)hitTestBlock {

    objc_setAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey), hitTestBlock, OBJC_ASSOCIATION_COPY);

}

- (STHitTestViewBlock)hitTestBlock {

    return objc_getAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey));

}

- (void)setPointInsideBlock:(STPointInsideBlock)pointInsideBlock {

    objc_setAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey), pointInsideBlock, OBJC_ASSOCIATION_COPY);

}

- (STPointInsideBlock)pointInsideBlock {

    return objc_getAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey));

}

@end

@implementation UIView (STHitTest)

const static NSString *STHitTestViewBlockKey = @&quot;STHitTestViewBlockKey&quot;;

const static NSString *STPointInsideBlockKey = @&quot;STPointInsideBlockKey&quot;;

+ (void)load {

    method_exchangeImplementations(class_getInstanceMethod(self, @selector(hitTest:withEvent:)),

                                   class_getInstanceMethod(self, @selector(st_hitTest:withEvent:)));

    method_exchangeImplementations(class_getInstanceMethod(self, @selector(pointInside:withEvent:)),

                                   class_getInstanceMethod(self, @selector(st_pointInside:withEvent:)));

}

- (UIView *)st_hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];

    UIView *superView = self.superview;

    while (superView) {

        [spaces appendString:@&quot;----&quot;];

        superView = superView.superview;

    }

    NSLog(@&quot;%@%@:[hitTest:withEvent:]&quot;, spaces, NSStringFromClass(self.class));

    UIView *deliveredView = nil;

    // 如果有hitTestBlock的實作,則調用block

    if (self.hitTestBlock) {

        BOOL returnSuper = NO;

        deliveredView = self.hitTestBlock(point, event, &amp;returnSuper);

        if (returnSuper) {

            deliveredView = [self st_hitTest:point withEvent:event];

        }

    } else {

        deliveredView = [self st_hitTest:point withEvent:event];

    }

//    NSLog(@&quot;%@%@:[hitTest:withEvent:] Result:%@&quot;, spaces, NSStringFromClass(self.class), NSStringFromClass(deliveredView.class));

    return deliveredView;

}

- (BOOL)st_pointInside:(CGPoint)point withEvent:(UIEvent *)event {

    NSMutableString *spaces = [NSMutableString stringWithCapacity:20];

    UIView *superView = self.superview;

    while (superView) {

        [spaces appendString:@&quot;----&quot;];

        superView = superView.superview;

    }

    NSLog(@&quot;%@%@:[pointInside:withEvent:]&quot;, spaces, NSStringFromClass(self.class));

    BOOL pointInside = NO;

    if (self.pointInsideBlock) {

        BOOL returnSuper = NO;

        pointInside =  self.pointInsideBlock(point, event, &amp;returnSuper);

        if (returnSuper) {

            pointInside = [self st_pointInside:point withEvent:event];

        }

    } else {

        pointInside = [self st_pointInside:point withEvent:event];

    }

    return pointInside;

}

- (void)setHitTestBlock:(STHitTestViewBlock)hitTestBlock {

    objc_setAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey), hitTestBlock, OBJC_ASSOCIATION_COPY);

}

- (STHitTestViewBlock)hitTestBlock {

    return objc_getAssociatedObject(self, (__bridge const void *)(STHitTestViewBlockKey));

}

- (void)setPointInsideBlock:(STPointInsideBlock)pointInsideBlock {

    objc_setAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey), pointInsideBlock, OBJC_ASSOCIATION_COPY);

}

- (STPointInsideBlock)pointInsideBlock {

    return objc_getAssociatedObject(self, (__bridge const void *)(STPointInsideBlockKey));

}

@end

代碼很簡單,就是利用iOS的runtime能力,在hitTest執行之前,插入了一個方法。如果有看不懂的,可以參考我以前的部落格 iOS面向切面程式設計

現在回到我們開始提出的題目,其實題目很簡單,就是簡單的可以把題目轉換為

如果我們觸摸點的坐标 point.x < 13, 我們就讓hit-Test 傳回NavigationController.view, 把所有的事件入口交給他,否則就傳回super,該怎麼處理怎麼處理

這樣就能滿足我們的條件,即使目前的VC上面有ScrollView,但是由于點選特定區域的時候,ScrollView根本得不到事件,是以系統會專心處理NavigationController的拖拽手勢,而不是ScrollView的事件,當沒有點選特定區域的時候,NavigationController的手勢不會觸發,系統會專心處理ScrollView的事件,互不影響,大家可以嘗試實作,代碼量不多。

雖然iOS8新增了UIScreenEdgePanGestureRecognizer 手勢,但是單純的用這個手勢無法解決目前VC上面有ScrollView的問題,有關手勢方面的事件分發,之後的文章會對此進行說明,這裡就不多說了。

當我們确定了HitTestView之後,我們的事件分發就正式開始了,如果hitTestView可以直接處理的,就處理,不能處理的,則交給 The Responder Chain/ GestureRecognizer。後續文章會對分發進行進一步說明。

附上一些測試查找hitTestView過程中列印的日志,可以觀察一下:

Objective-C

STPWindow:[hitTest:withEvent:]

----UIView:[hitTest:withEvent:]

--------STPView:[hitTest:withEvent:]

--------UICollectionView:[hitTest:withEvent:]

------------UIImageView:[hitTest:withEvent:]

------------UIImageView:[hitTest:withEvent:]

------------STDefaultRefreshControl:[hitTest:withEvent:]

------------STPFeedCell:[hitTest:withEvent:]

------------STPFeedCell:[hitTest:withEvent:]

----------------UIView:[hitTest:withEvent:]

--------------------UIImageView:[hitTest:withEvent:]

------------------------UIImageView:[hitTest:withEvent:]

------------------------UIView:[hitTest:withEvent:]

------------------------STImageView:[hitTest:withEvent:]

STPWindow:[hitTest:withEvent:]

----UIView:[hitTest:withEvent:]

--------STPView:[hitTest:withEvent:]

--------UICollectionView:[hitTest:withEvent:]

------------UIImageView:[hitTest:withEvent:]

------------UIImageView:[hitTest:withEvent:]

------------STDefaultRefreshControl:[hitTest:withEvent:]

------------STPFeedCell:[hitTest:withEvent:]

------------STPFeedCell:[hitTest:withEvent:]

----------------UIView:[hitTest:withEvent:]

--------------------UIImageView:[hitTest:withEvent:]

------------------------UIImageView:[hitTest:withEvent:]

------------------------UIView:[hitTest:withEvent:]

------------------------STImageView:[hitTest:withEvent:]

其中—-表示View的層次結構

========

ios開發 在UiView中添加點選事件

雖然swift語言已經釋出,但就目前而言,開發iosApp還是用objective c來完成,此處說的是objective c語言中的。

在app的開發過程中我們常常需要在一些非button中添加一些點選事件,來實作我們想要的效果。比如做個下拉選項,我們希望點選背景時選項視圖消失,或者我們點選某個圖檔(uiimageView)時跳轉到大圖頁面或者做其他操作。也許初學者會郁悶。。。這些怎麼添加點選事件呢?點選事件不是隻有button才能添加麼?其實隻要是繼承 uiview的空間,你都可以手動的添加一些點選事件。

讓我們看看是怎麼實作的吧。

UITapGestureRecognizer*tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:selfaction:@selector(Actiondo:)];

    [uiview addGestureRecognizer:tapGesture];

其實了解起來也很簡單,就是建立一個UITapGestureRecognizer,這個是點選事件,再将這個事件加到uiview中,繼承uiview的一般都有addGestureRecognizer這個方法。addGestureRecognizer方法就是用于添加點選事件的。

我們再定義一個Actiondo的響應方法。

-(void)Actiondo:(id)sender{}

将我們需要的動作添加在其中就可以了。

好了,UITapGestureRecognizer的使用就是這樣了。趕快試一下吧!

========

UITextView 響應 鍵盤的return(完成鍵)

http://blog.sina.com.cn/u/1509658847

UITextView <wbr>響應 <wbr>鍵盤的return(完成鍵)

UITextFieldDelegate代理裡面響應return鍵的回調:textFieldShouldReturn:。

但是 UITextView的代理UITextViewDelegate 裡面并沒有這樣的回調。

但是有别的方法可以實作:

UITextViewDelegate裡面有這樣一個代理函數:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text

這個函數的最後一個參數text代表你每次輸入的的那個字,是以:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{

    if ([text isEqualToString:@"\n"]){ //判斷輸入的字是否是回車,即按下return

        //在這裡做你響應return鍵的代碼

        return NO; //這裡傳回NO,就代表return鍵值失效,即頁面上按下return,不會出現換行,如果為yes,則輸入頁面會換行

    }

    return YES;

}

========

http://blog.csdn.net/jianxin160/article/details/47753217

iOS開發系列--觸摸事件、手勢識别、搖晃事件、耳機線控