1. 程式人生 > >iOS 點選事件傳遞及響應

iOS 點選事件傳遞及響應

關於iOS的事件響應機制網上講解文章不少,有的文章內容少沒講全面,有的說的太多,一個概念反覆說,傳遞和響應混在一起講,不好理解,我綜合參考了幾篇文章總結了一下,覺得可以分為以下幾點來講

  1. iOS中的事件介紹
  2. 事件的產生和傳遞
  3. 事件響應
  4. 實際專案中的應用

1.iOS中的事件介紹

iOS中的事件可以分為3大型別:

  • 觸屏事件(例如點選按鈕、通過手勢縮放圖片、拖動上下滾動頁面等)
  • 加速計事件(例如搖一搖紅包、通過旋轉裝置控制賽車方向、指南針等)
  • 遠端控制事件(例如耳機的線控、外接手柄、遙控器等)

iOS處理觸屏事件,分為兩種方式:

  • 高階事件處理:利用UIKit提供的各種使用者控制元件或者手勢識別器來處理事件。
  • 低階事件處理:在UIView的子類中重寫觸屏回撥方法,直接處理觸屏事件。

如果想監聽一個view上面的觸控事件,之前的做法是:

(1)自定義一個view。

(2)實現view的touches方法,在方法內部實現具體處理程式碼。

通過touches方法監聽view觸控事件,有很明顯的幾個缺點:

(1)必須得自定義view。

(2)由於是在view內部的touches方法中監聽觸控事件,因此預設情況下,無法讓其他外界物件監聽view的觸控事件。

(3)不容易區分使用者的具體手勢行為。

iOS 3.2之後,蘋果推出了手勢識別功能(Gesture Recognizer),在觸控事件處理方面,大大簡化了開發者的開發難度。

UIKit中我們常用的是UIControl類例項的addTarget:action:forControlEvents:方法維護控制元件目標行為表,除了UIKit控制元件外,手勢識別器UIGestureRecognizer類的例項也是處理觸屏事件的好幫手,其內部也使用目標行為表。

為什麼手勢和單擊事件只會響應手勢?一種猜測:

123456789101112 -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{  if(gesture!=nil)  {    [gesture method];  }  else  {     [self performSelector:bthmethod: withObject:self ];  }}
12345678 UIKit內建了6種手勢識別器:UITapGestureRecognizer:點選(單擊、雙擊、三連擊等)手勢。UIPinchGestureRecognizer:縮放手勢。UIPanGestureRecognizer:拖拽手勢。UISwipeGestureRecognizer:滑動手勢。UIRotationGestureRecognizer:旋轉手勢。UILongPressGestureRecognizer:長按手勢。

UIKit控制元件和手勢識別器屬於高階事件處理的範疇,這些不再多說,以下文字都是介紹的低階事件處理過程

UITouch

當你用一根手指觸控式螢幕幕時, 會建立一個與之關聯的UITouch物件, 一個UITouch物件對應一根手指. 在事件中可以根據NSSet中UITouch物件的數量得出此次觸控事件是單指觸控還是雙指多指等等

1234567891011 觸控產生時所處的視窗@property(nonatomic,readonly,retain) UIWindow *window;觸控產生時所處的檢視@property(nonatomic,readonly,retain) UIView *view;短時間內點按螢幕的次數,可以根據tapCount判斷單擊、雙擊或更多的點選@property(nonatomic,readonly) NSUInteger tapCount;記錄了觸控事件產生或變化時的時間,單位是秒@property(nonatomic,readonly) NSTimeInterval timestamp;當前觸控事件所處的狀態@property(nonatomic,readonly) UITouchPhase phase;

UIEvent

每產生一個事件, 就對應產生一個UIEvent. UIEvent記錄著該事件產生的時間, 事件的型別等等

UIEvent幾個重要的屬性 :

123456 事件型別@property(nonatomic,readonly) UIEventType type;@property(nonatomic,readonly) UIEventSubtype subtype;事件產生的時間@property(nonatomic,readonly) NSTimeInterval timestamp;

響應者物件(UIResponder)

在iOS中不是任何物件都能處理事件, 只有繼承了UIResponder的物件才能接收並處理事件,我們稱為響應者物件
UIApplication,UIViewController,UIView都繼承自UIResponder,因此他們都是響應者物件, 都能夠接收並處理事件

繼承自UIResponder的類能處理事件是由於UIResponder內部提供了以下方法

12345678910111213 觸控事件- (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 *)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;

兩個UIView相關屬性:

  • multipleTouchEnabled:是否開啟多點觸控
  • exclusiveTouch :多個控制元件接受事件時的排他性

2. iOS中事件的產生和傳遞

1.發生觸控事件後,系統會將該事件加入到一個由UIApplication管理的佇列事件中

2.UIApplication會從事件佇列中取出最前面的事件,並將事件分發下去以便處理,通常會先發送事件給應用程式的主視窗(keyWindow)

3.主視窗會在檢視層次結構中找到一個最合適的檢視來處理觸控事件

事件的具體傳遞過程,如圖:

事件的具體傳遞圖

一般事件的傳遞是從父控制元件傳遞到子控制元件的

例如:

點選了綠色的View,傳遞過程如下:UIApplication->Window->白色View->綠色View

點選藍色的View,傳遞過程如下:UIApplication->Window->白色View->橙色View->藍色View

如果父控制元件接受不到觸控事件,那麼子控制元件就不可能接收到觸控事件

( 重難點)如何尋找最合適的view

12345678910111213 應用如何找到最合適的控制元件來處理事件?有以下準則1.首先判斷主視窗(keyWindow)自己是否能接受觸控事件2.觸控點是否在自己身上3.從後往前遍歷子控制元件,重複前面的兩個步驟(首先查詢陣列中最後一個元素)4.如果沒有符合條件的子控制元件,那麼就認為自己最合適處理詳述:1.主視窗接收到應用程式傳遞過來的事件後,首先判斷自己能否接手觸控事件。如果能,那麼在判斷觸控點在不在視窗自己身上2.如果觸控點也在視窗身上,那麼視窗會從後往前遍歷自己的子控制元件(遍歷自己的子控制元件只是為了尋找出來最合適的view)3.遍歷到每一個子控制元件後,又會重複上面的兩個步驟(傳遞事件給子控制元件,1.判斷子控制元件能否接受事件,2.點在不在子控制元件上)4.如此迴圈遍歷子控制元件,直到找到最合適的view,如果沒有更合適的子控制元件,那麼自己就成為最合適的view。注意:之所以會採取從後往前遍歷子控制元件的方式尋找最合適的view只是為了做一些迴圈優化。因為相比較之下,後新增的view在上面,降低迴圈次數。

UIView不能接收觸控事件的三種情況:

  • 不接受使用者互動:userInteractionEnabled = NO;

  • 隱藏:hidden = YES;

  • 透明:alpha = 0.0~0.01

尋找最合適的view過程,如圖:

尋找最合適的view過程圖

說明一下控制元件的新增順序:白1->綠2->橙2->藍3->紅3->黃4

這裡點選了橙色的那塊區域,事件傳遞判斷過程如下:

1.UIApplication從事件佇列中取出事件分發給UIWindow

2.UIWindow判斷自己是否能接受觸控事件,可以

3.UIWindow判斷觸控點是否在自己身上,是的。

4.UIWindow從後往前便利自己的子控制元件,取出白1

5.白1都滿足最上面兩個條件,遍歷子控制元件橙2

6.橙2都滿足最上面兩個條件,遍歷子控制元件,先取出紅3

7.紅3不滿足條件2,取出藍3

8.藍3也不滿足條件2,最後最合適的控制元件是橙2

在事件傳遞尋找最合適的View時,底層到底幹了哪些事?

尋找合適的View用到兩個重要方法:

  • hitTest:withEvent:

  • pointInside

hitTest:withEvent:方法

什麼時候呼叫?

  • 只要事件一傳遞給一個控制元件,這個控制元件就會呼叫他自己的hitTest:withEvent:方法尋找合適的View
1234 作用尋找並返回最合適的view(能夠響應事件的那個最合適的view)注 意:不管這個控制元件能不能處理事件,也不管觸控點在不在這個控制元件上,事件都會先傳遞給這個控制元件,隨後再呼叫hitTest:withEvent:方法

hitTest:withEvent:底層呼叫流程:

底層呼叫流程圖

123456789101112131415161718192021 底層具體實現如下 :- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ // 1.判斷當前控制元件能否接收事件 if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 2. 判斷點在不在當前控制元件 if ([self pointInside:point withEvent:event] == NO) return nil; // 3.從後往前遍歷自己的子控制元件 NSInteger count = self.subviews.count; for (NSInteger i = count - 1; i >= 0; i--) { UIView *childView = self.subviews[i]; // 把當前控制元件上的座標系轉換成子控制元件上的座標系 CGPoint childP = [self convertPoint:point toView:childView]; UIView *fitView = [childView hitTest:childP withEvent:event]; if (fitView) { // 尋找到最合適的view return fitView; } } // 迴圈結束,表示沒有比自己更合適的view return self;}

事件傳遞給視窗或控制元件的後,就呼叫hitTest:withEvent:方法尋找更合適的view。所以是,先傳遞事件,再根據事件在自己身上找更合適的view。不管子控制元件是不是最合適的view,系統預設都要先把事件傳遞給子控制元件,經過子控制元件呼叫自己的hitTest:withEvent:方法驗證後才知道有沒有更合適的view。即便父控制元件是最合適的view了,子控制元件的hitTest:withEvent:方法還是會呼叫,不然怎麼知道有沒有更合適的!即,如果確定最終父控制元件是最合適的view,那麼該父控制元件的子控制元件的hitTest:withEvent:方法也是會被呼叫的。

hitTest:withEvent:方法忽略隱藏(hidden=YES)的檢視,禁止使用者操作(userInteractionEnabled=YES)的檢視,以及alpha級別小於0.01(alpha<0.01)的檢視。
如果一個子檢視的區域超過父檢視的bound區域(父檢視的clipsToBounds 屬性為NO,這樣超過父檢視bound區域的子檢視內容也會顯示),那麼正常情況下對子檢視在父檢視之外區域的觸控操作不會被識別,因為父檢視的pointInside:withEvent:方法會返回NO,這樣就不會繼續向下遍歷子檢視了。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 
該方法判斷觸控點是否在控制元件身上, 是則返回YES, 否則返回NO

作用
可以使用以上兩個方法做到:
指鹿為馬(明明點選的是B檢視, 卻由A檢視來響應事件)
穿透某控制元件點選被覆蓋的下一層控制元件
讓父控制元件frame之外的子控制元件響應觸控事件(下面實際應用中有具體介紹)

3.事件響應

上文介紹了事件的傳遞過程,找到合適的View之後就會呼叫該view的touches方法要進行響應處理具體的事件,找不到最合適的view,就不會呼叫touches方法進行事件處理。

這裡先介紹一下響應者鏈條:響應者鏈條其實就是很多響應者物件(繼承自UIResponder的物件)一起組合起來的鏈條稱之為響應者鏈條

一般預設做法是控制元件將事件順著響應者鏈條向上傳遞,將事件交給上一個響應者進行處理 (即呼叫super的touches方法)。

那麼如何判斷當前響應者的上一個響應者是誰呢?有以下兩個規則:

1.判斷當前是否是控制器的View,如果是控制器的View,上一個響應者就是控制器

2.如果不是控制器的View,上一個響應者就是父控制元件

響應過程如下圖:

響應過程圖

touch響應:

  • 找到最合適的view會呼叫touches方法處理事件
  • touches預設做法是把事件順著響應者鏈條向上拋
12345678 //只要點選控制元件,就會呼叫touchBegin,如果沒有重寫這個方法,自己處理不了觸控事件// 上一個響應者可能是父控制元件- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ // 預設會把事件傳遞給上一個響應者,上一個響應者是父控制元件,交給父控制元件處理[super touchesBegan:touches withEvent:event]; // 注意不是呼叫父控制元件的touches方法,而是呼叫父類的touches方法// super是父類 superview是父控制元件 }

如果控制器也不響應響應touches方法,就交給UIWindow。如果UIWindow也不響應,交給UIApplication,如果都不響應事件就作廢了。

用具體的例子來看下響應過程,例子太簡單就不上demo了,看下截圖(3-2)


(圖3-2)

控制元件的新增順序:紅1->藍2->綠2->黃3

綠色View實現了touch方法,如下

黃色view沒有實現touch方法,如下

當點選黃色區域時,由於黃色view沒有實現touch方法,就順著響應鏈找到其父view(綠色View),綠色view實現了touch方法,便列印了– touchGreen

最後總結來說一次完整的觸控事件的傳遞響應過程為:

UIApplication–>UIWindow–>遞迴找到最合適處理的控制元件–>控制元件呼叫touches方法–>判斷是否實現touches方法–>沒有實現預設會將事件傳遞給上一個響應者–>找到上一個響應者–>找不到方法作廢

一句話總結整個過程是:觸控或者點選一個控制元件,然後這個事件會從上向下(從父->子)找最合適的view處理,找到這個view之後看他能不能處理,能就處理,不能就按照事件響應鏈向上(從子->父)傳遞給父控制元件

事件的傳遞和響應的區別:
事件的傳遞是從上到下(父控制元件到子控制元件),事件的響應是從下到上(順著響應者鏈條向上傳遞:子控制元件到父控制元件。

4 實際專案中的應用

  • 情景1: 點選子控制元件,讓父控制元件響應事件;(點選綠色View,紅色View響應)

分析:可通過兩種方式實現

(1)因為hitTest:withEvent:方法的作用就是控制元件接收到事件後,判斷自己是否能處理事件,判斷點在不在自己的座標系上,然後返回最合適的view。所以,我們可以在hitTest:withEvent:方法裡面強制返回父控制元件為最合適的view.

123456789101112 #import "GreenView2.h"@implementation GreenView2- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ return [self superview]; // return nil; // 此處返回nil也可以。返回nil就相當於當前的view不是最合適的view}@end

(2) 讓誰響應,就直接重寫誰的touchesBegan: withEvent:方法

123456789101112 #import "RedView1.h"@implementation RedView1-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"-- touchRed touchesBegan");}@end
  • 情景2:點選子控制元件,父控制元件和子控制元件都響應事件

    分析:事件的響應是順著響應者鏈條向上傳遞的,即從子控制元件傳遞給父控制元件,touch方法預設不處理事件,而是把事件順著響應者鏈條傳遞給上一個響應者。這樣我們就可以依託這個原理,讓一個事件多個控制元件響應

    12345678910 #import "GreenView2.h"@implementation GreenView2-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"-- touchGreen"); [super touchesBegan:touches withEvent:event];}

參考資料:

相關推薦

iOS 事件傳遞響應

關於iOS的事件響應機制網上講解文章不少,有的文章內容少沒講全面,有的說的太多,一個概念反覆說,傳遞和響應混在一起講,不好理解,我綜合參考了幾篇文章總結了一下,覺得可以分為以下幾點來講 iOS中的事件介紹事件的產生和傳遞事件響應實際專案中的應用 1.iOS中的事件介紹

iOS —— 觸控事件傳遞響應與手勢

iOS 的事件分為三種,觸控事件(Touch Event)、加速器事件(Motion Events)、遠端遙控事件(Remote Events)。這些事件對應的類為UIResponder。本文只探究觸控事件。 Tip:在模擬器中,按住option可以兩根手指操作。同時按住option+shift可以移動兩根

iOS UIButton事件傳遞引數的解決辦法

一、問題的出現 原生的UIButton的點選事件唯一的引數就是UIButton本身,我們通常使用UIButton自帶的tag來使用不同的引數,在簡單的業務場景下,通過tag都是可以滿足需求的,但是在某些業務複雜的情況下,tag顯得有些無力了,畢竟通過tag來傳

IOS效果實現後物件引數的傳遞

IOS點選實現的方式: 1、使用UIButton,這個不用說大家都知道: UIButton *btn=[[UIButton allloc] init]; [btn addTarget:self action:@selector(OnTapBtn:) forControlEv

ios h5 事件失效延遲

1.ios h5 app avalon tap事件失效 使用MUI製作app介面,使用avalon.js渲染資料,發現在(Android上正常)ios上執行時容器div的avalon的ms-on-tap被內容遮住不執行.用MUI的on tap事件可以執行但改動較大,於是

微信小程式事件傳遞引數的方法

小程式在元件上繫結事件後,傳遞引數的方式不同於前端開發其他場景中直接加引數的方式,小程式在引數的傳遞時,採用事件物件的自定義屬性的方式,具體實現如下: wxml: <view bindtap="passQuery" data-index="1">點選事件傳參</view&g

jquery父元素和子元素事件傳遞問題_不可把父元素的事件傳遞給子元素_事件無限迴圈傳遞

前述:jquery中: 當一個元素的點選事件被觸發時,會自動將該事件向父級元素逐級專遞。 但是實際場景當中,我們可能會遇到需要在父級元素中定義點選事件,來觸發特定子元素的點選事件,我就遇到了這麼一個問題。  但是這麼做的後果,在jquery1.8.2版本及以後所有版本(截止目前最新版本為3.3),

Android事件傳遞機制詳解

在講正題之前我們講一段有關任務傳遞的小故事,拋磚迎玉下: 話說一家軟體公司,來一個任務,分派給了開發經理去完成: 開發經理拿到,看了一下,感覺好簡單,於是 開發經理:分派給了開發組長 開發組長:分派給了自己組員(程式設計師) 程式設計師:分派給了自己帶的實習生。

在UIView中新增事件ocswift

UIView繼承於UIResponder是沒有addTarget 方法的,所有隻能在UIView上新增手勢UITapGestureRecognizer來實現點選事件。 首先設定UIView(或其子類)為可互動的: oc: iconView.userIn

foreach事件傳遞引數,處理後區域性重新整理

1、foreach點選事件傳遞引數 </c:forEach var="liveInfo" items="${liveInfos }"> <div id="th

react native事件傳遞引數

比如我們定義一個TouchableOpacity點選事件,該方法需要接收一個引數值,如下 _gotoSubClass(sectionID, rowID) { console.log("sect

EditText的事件遮蔽鍵盤響應

在這是這個屬性前,每次都是點選EditText先獲取焦點,然後再點選下才執行onClick事件。 為了實現點選一次就執行onClick的解決辦法: 在XML里加個android:focusableIn

8.0通知欄新增通知渠道,自定義通知 響應事件

今天寫了下demo 發現通知在8.0後有些改變記錄下這個坑!!!! 通知的程式碼大家在網上一搜一大把,我也不廢話 直接上重點。 點擊發送通知,沒有通知顯示,程式碼如下。 日誌資訊 明明走了這行程式碼卻什麼也沒發生,what? 難道寫錯了?,後來查看了資料下才知道是8

iOS開發中兩層view上的button不響應事件

iOS button addTarget 無法響應事件 1.問題描述 封裝了一個XYAlterview,繼承於UIView,但button addTarget 無法響應事件. 2.問題重現 @interface XYAlertView : UIView @end XYAlt

iOS中超出父檢視的按鈕事件響應處理

在iOS開發中會遇到一些設計樣式,需要把按鈕一部分懸空在父檢視的上面,但是當我們點選該按鈕時,超出了父檢視的懸空部分不會響應該按鈕的點選事件。 原理就是iOS的touch事件的相應是從最下方的父檢視開始的,系統判斷點選的座標點上沒有子檢視,所以不再響應,該原理可參見之前寫過

VLC控制元件使用事件響應

下載並安裝VLC控制元件 這個axvlc.dll就是IE下的vlc外掛,自己封裝vlc時,將這個axvlc.dll註冊就可以使用vlc控制元件。 regsvr32 XX/axvlc.dll 呼叫vlc <object type='appli

iOS開發中oc程式碼響應js網頁事件的方法

在APP開發中我們經常碰到一種情況:app開啟一個網頁,網頁裡有些按鈕需要我們去響應,比如開啟一個遊戲介紹的網頁,點選網頁中的“立即下載”按鈕,app需要跳轉到對應下載介面。那麼我們怎麼響應?程式碼如下: -(void)makeWeb { self.webView

iOS開發-UIImageView響應事件

UIImageView是不能夠響應點選事件的,在開發過程中我們需要經常對頭像等新增點選事件,上網搜尋一番後發現有如下兩個方法: 1.找到點選圖片Event,新增事件處理函式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 UIImageView.userInteraction

ios 手勢子檢視的時候不響應父檢視的事件

手勢點選子檢視的時候不響應父檢視的點選事件; 一句程式碼搞定: if( CGRectContainsPoint(_BGView.frame, [sender locationInView:_

iOS中按鈕不響應事件

今天寫iOS專案時,發現雖然為按鈕添加了點選事件,但是點選後卻無法響應,搜尋後才解決了問題:按鈕的所在的父控制元件如果不能互動的話,那麼這個按鈕也無法互動,當然,可以將父控制元件的userInterac