iOS事件攔截(實現觸摸任意位置隱藏指定view)

項目裏有一個需求,類似新浪或者騰訊微博的頂部title欄的類別選擇器的消失(在選擇器展開的時候,觸摸屏幕任何地方使其消失)。

最開始的想法是當這個選擇器(selectorView)展開的時候,在當前屏幕上加入一個鋪滿整個屏幕的透明button來攔截所有的觸摸事件。

可是這個方案實現起來非常麻煩,也不優雅,而且發現button攔截不到scrollView的滑動事件,所以決定放棄。

後來經過經理提醒,在UIApplication下有一個sendEvent函數,可以從這裏入手。

於是找了一下iOS事件機制的資料,sendEvent函數的介紹如下:

sendEvent:

Dispatches an event to the appropriate responder objects in the application.

- (void)sendEvent:(UIEvent *)event
Parameters
event

UIEvent object encapsulating the information about an event, including the touches involved.

Discussion

Subclasses may override this method to intercept incoming events for inspection and special dispatching. iOS calls this method for public events only.

Availability
  • Available in iOS 2.0 and later.

 摘抄《iOS程序之事件處理流程》資料:

在iOS系統中有個很重要的概念:Responder。基本上所有的UI相關的控件,view和viewcontroller都是繼承自UIResponder。事件的分發正是通過由控件樹所構成的responder chain(響應鏈)所進行的。一個典型的iOS響應鏈如下:

                                              uikit_responder_chain

當用戶發起一個事件,比如觸摸屏幕或者晃動設備,系統產生一個事件,同時投遞給UIApplication,而UIApplication則將這個事件傳遞給特定的UIWindow進行處理(正常情況都一個程序都只有一個UIWindow),然後由UIWindow將這個事件傳遞給特定的對象(即first responder)並通過響應鏈進行處理。雖然都是通過響應鏈對事件進行處理,但是觸摸事件和運動事件在處理上有着明顯的不同(主要體現在確定哪個對象纔是他們的first responder):

看起來很對路,觸摸事件發生後,會先經過hitTest確定觸摸事件發生在哪個view上,然後該事件會經由sendEvent分發到“合適”的對象進行處理,也就是說sendEvent相當於事件的中轉站,在這裏可以攔截所有的iOS事件。

        在iOS系統中,一共有三種形式的事件:觸摸事件(Touch Event),運動事件(Motion Event)和遠端控制事件(Remote-control Event)。顧名思義,觸摸事件就是當用戶觸摸屏幕時發生的事件,而運動事件是用戶移動設備時發生的事件:加速計,重力感應。遠端控制事件可能比較陌生:如通過耳機進行控制iOS設備聲音等都屬於遠端控制事件—-下面不展開說,因爲和主題無關,詳細的內容可以參考: 《Remote Control of Multimedia》 。

於是理了一下思路,決定就從它入手。

具體流程是這樣:

1.新建一個自定義的UIApplication(MyApplication),並替換系統默認的UIApplication:

  在程序入口處(main.m)修改代碼,這樣程序就會調用我們的自定義Application類

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, NSStringFromClass([MyApplication class]), NSStringFromClass([AppDelegate class]));
    }
}

2.在MyApplication中實現sendEvent函數,利用系統通知中心(NSNotificationCenter)發送觸摸事件:

-(void)sendEvent:(UIEvent *)event
{
    if (event.type==UIEventTypeTouches) {
        if ([[event.allTouches anyObject] phase]==UITouchPhaseBegan) {
            //響應觸摸事件(手指剛剛放上屏幕)
            [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:nScreenTouch object:nil userInfo:[NSDictionary dictionaryWithObject:event forKey:@"data"]]];
            //發送一個名爲‘nScreenTouch’(自定義)的事件
        }
    }
    [super sendEvent:event];
}

3.在selectorView的構造函數中註冊nScreenTouch事件,並判斷該次觸摸時間是否由selectorView引發,如果不是,則隱藏selectorView。

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
        //註冊nScreenTouch事件
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onScreenTouch:) name:nScreenTouch object:nil];
        
    }
    return self;
}
-(void)dealloc
{
    //移除nScreenTouch事件
    [[NSNotificationCenter defaultCenter] removeObserver:self name:nScreenTouch object:nil];
    [super dealloc];
}
-(void) onScreenTouch:(NSNotification *)notification
{
    UIEvent *event=[notification.userInfo objectForKey:@"data"];
    UITouch *touch=[event.allTouches anyObject];
    if (touch.view!=self) {
        //取到該次touch事件的view,如果不是觸摸了selectorView,則隱藏selectorView.
        [UIView animateWithDuration:0.5 animations:^
        {
            self.alpha=0;
        }];
        [UIView commitAnimations];
    }
}

這樣就實現了觸摸任意地方,能隱藏彈出窗口的需求。相比較添加隱藏view的方案,這個方案更優雅,只是性能可能會有點損耗,但是可以通過添加全局的開關來控制發送消息的時機(比如只有當selectorView顯示之後,才發送那個事件)。

總結

通過sendEvent配合消息中心,可以實現很多看起來挺複雜的功能,而且從解耦的角度,也非常優雅。

發佈了4 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章