iOS中事件分為
- 觸摸事件
- 加速計(jì)事件
- 遠(yuǎn)程控制事件
響應(yīng)者對(duì)象
能夠接收并處理事件的對(duì)象,必須是UIResponder的子類。比如:UIApplication、UIWindow、UIView等。
UIResponder處理事件的幾種方法
1、觸摸事件
// 一根或者多根手指開始觸摸
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手機(jī)在屏幕上移動(dòng),只要移動(dòng)就會(huì)觸發(fā)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手指離開屏幕
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 結(jié)束觸摸前,由系統(tǒng)強(qiáng)制退出或者來(lái)電,事件會(huì)被取消
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 預(yù)估觸摸特性,當(dāng)從邊緣進(jìn)入,系統(tǒng)不知道當(dāng)前觸摸特性,會(huì)被調(diào)用下列方法
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches;
2、加速計(jì)事件
// 開始動(dòng)起來(lái)
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
// 運(yùn)動(dòng)結(jié)束
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
// 運(yùn)動(dòng)被打斷,取消
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
3、遠(yuǎn)程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
UITouch
- 觸摸事件中,touches中存放的是UITouch對(duì)象,一根手指對(duì)應(yīng)一個(gè)UITouch對(duì)象
- 觸摸開始,系統(tǒng)會(huì)創(chuàng)建手指對(duì)應(yīng)的UITouch對(duì)象,隨著手指移動(dòng),UITouch對(duì)象會(huì)隨著更新,觸摸事件結(jié)束,系統(tǒng)會(huì)銷毀這個(gè)UITouch對(duì)象
1、屬性
// 觸摸產(chǎn)生時(shí)對(duì)應(yīng)觸摸的窗口
@property(nullable,nonatomic,readonly,strong) UIWindow *window;
// 觸摸產(chǎn)生時(shí)對(duì)應(yīng)的觸摸視圖
@property(nullable,nonatomic,readonly,strong) UIView *view;
// 觸摸事件產(chǎn)生或者變化的時(shí)間(單位秒)
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 當(dāng)前觸摸事件的狀態(tài)(開始:Began、移動(dòng):Moved、靜止:Stationary、結(jié)束:Ended、取消Cancelled)
@property(nonatomic,readonly) UITouchPhase phase;
// 短時(shí)間的觸摸次數(shù),據(jù)此判斷是幾根手指觸摸,雙擊、單擊或者其他更多
@property(nonatomic,readonly) NSUInteger tapCount;
// 觸摸手勢(shì)組
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers;
# pragma mark - iOS8新增屬性
// 觸摸的主半徑
@property(nonatomic,readonly) CGFloat majorRadius;
// 觸摸主半徑的公差
@property(nonatomic,readonly) CGFloat majorRadiusTolerance;
# pragma mark - iOS9新增屬性
// 觸摸類型(直接手指觸摸:Direct、間接觸摸:Indirect、觸摸筆觸摸:Stylus)
@property(nonatomic,readonly) UITouchType type;
// 觸摸力度,其中1.0表示平均力壓值
@property(nonatomic,readonly) CGFloat force;
// 能觸摸的最大力度
@property(nonatomic,readonly) CGFloat maximumPossibleForce;
// 高角度,僅適用于用觸摸筆觸摸類型。0:觸摸筆平行于平面;M_PI/2:觸摸筆垂直于平面
@property(nonatomic,readonly) CGFloat altitudeAngle;
// 預(yù)估更新索引,可以讓更新的觸摸對(duì)象與原對(duì)象進(jìn)行關(guān)聯(lián),當(dāng)前觸摸對(duì)象的觸摸特性變化,該值就會(huì)加一
@property(nonatomic,readonly) NSNumber * _Nullable estimationUpdateIndex;
// 預(yù)估屬性,當(dāng)前觸摸對(duì)象的預(yù)估觸摸特性(力度:Force、方位角:Azimuth、高度:Altitude、位置:Location)
@property(nonatomic,readonly) UITouchProperties estimatedProperties;
// 預(yù)期將要更新的特性,如果沒有更新,則當(dāng)前值為最終預(yù)估值,用于從邊緣進(jìn)入時(shí)系統(tǒng)開始無(wú)法獲得準(zhǔn)確的touches值
@property(nonatomic,readonly) UITouchProperties estimatedPropertiesExpectingUpdates;
2、方法
// 返回觸摸在view上的坐標(biāo)位置(以view左上角原點(diǎn)(0, 0)為準(zhǔn)),若傳入的view = nil,則返回觸摸點(diǎn)在window上的位置
- (CGPoint)locationInView:(nullable UIView *)view;
// 同上,這個(gè)方法記錄了上一個(gè)觸摸點(diǎn)的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;
# pragma mark - iOS9新增方法
// 精準(zhǔn)的返回觸摸在view上的坐標(biāo)位置
- (CGPoint)preciseLocationInView:(nullable UIView *)view;
// 精準(zhǔn)的返回上一個(gè)在view上的觸摸位置
- (CGPoint)precisePreviousLocationInView:(nullable UIView *)view;
// 返回觸摸在view上的方位角(僅適用于觸摸筆觸摸事件)
- (CGFloat)azimuthAngleInView:(nullable UIView *)view;
// 返回觸摸在view上的指向方向角的單位向量(僅適用于觸摸筆觸摸事件)
- (CGVector)azimuthUnitVectorInView:(nullable UIView *)view;
UIEvent
- 每產(chǎn)生一個(gè)事件就會(huì)產(chǎn)生一個(gè)UIEvent對(duì)象
- UIEvent對(duì)象記錄了事件的產(chǎn)生時(shí)刻和類型
1、屬性
// 事件類型(Touches:點(diǎn)擊、Motion:加速計(jì)、RemoteControl:遠(yuǎn)程控制、Presses:按壓)
@property(nonatomic,readonly) UIEventType type;
/**
同一事件類型的具體事件
None:type是觸摸
MotionShake:type是加速計(jì)
== 剩下是針對(duì)遠(yuǎn)程控制事件類型 ==
RemoteControlPlay:播放
RemoteControlPause:暫停
RemoteControlStop:停止
RemoteControlTogglePlayPause:切換播放暫停
RemoteControlNextTrack:下一曲
RemoteControlPreviousTrack:上一曲
RemoteControlBeginSeekingBackward:開始往后尋找(開始快退)
RemoteControlEndSeekingBackward:結(jié)束向后尋找(結(jié)束快退)
RemoteControlBeginSeekingForward:開始向前尋找(開始快進(jìn))
RemoteControlEndSeekingForward:結(jié)束向前尋找(結(jié)束快進(jìn))
*/
@property(nonatomic,readonly) UIEventSubtype subtype;
// 事件產(chǎn)生的時(shí)間
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 事件中產(chǎn)生的所有觸摸點(diǎn)
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
2、方法
// 返回在窗口上的所有觸摸點(diǎn)
- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;
// 返回在當(dāng)前視圖上的所有觸摸點(diǎn)
- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
// 返回事件中手勢(shì)的觸摸點(diǎn)
- (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture
# pragma mark - iOS9新增方法
// 將丟失的觸摸點(diǎn)放在一個(gè)數(shù)組中返回(手機(jī)掃描渲染是每秒60幀,會(huì)出現(xiàn)繪畫延遲,所以有些觸摸點(diǎn)會(huì)丟失,下列方法是針對(duì)這種情況的優(yōu)化)
- (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch;
// 預(yù)測(cè)的一組觸摸點(diǎn)位置,但是不定和實(shí)際觸摸點(diǎn)一樣,所以是預(yù)估值
- (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch;
不能接收事件情況:
- 不能接收用戶交互,self.userInteractionEnabled = NO
- 被隱藏,self.hidden = YES
- 透明度小于0.01,self.alpha < 0.01
- 如果父控件不能接收觸摸事件,那么子控件也將不能接收
觸摸事件中事件的產(chǎn)生和傳遞
1、事件發(fā)生后,系統(tǒng)會(huì)將事件加入到UIApplication管理的隊(duì)列中
2、UIApplication遵循先進(jìn)先出的原則,將隊(duì)列中先進(jìn)的事件發(fā)送給主窗口(keyWindow)
3、主窗口會(huì)在視圖層級(jí)中找到一個(gè)合適的view來(lái)接收和處理事件
4、找到合適的view后,會(huì)調(diào)用視圖的touches方法來(lái)具體處理事件
具體尋找合適的view
1、先判斷view是否能接收觸摸事件
2、觸摸點(diǎn)是否在view身上
3、在子控件數(shù)組中從后向前遍歷子控件,然后重復(fù)上面兩步,找到則返回合適的子控件
4、沒找到合適的子控件,則自己就是合適處理的
選出最佳view方法
// 事件一傳遞給控件,就會(huì)調(diào)用下列方法,返回最合適接收事件的UIView
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
// 默認(rèn)值是 YES,返回觸摸點(diǎn)是否在調(diào)用者身上
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
事件傳遞詳細(xì)過(guò)程
1、觸摸事件產(chǎn)生和傳遞后,找到最佳接收和處理事件的view
2、插入說(shuō)明:響應(yīng)者鏈條由事件響應(yīng)者組成
3、如果最佳視圖調(diào)用了[super touches...],則事件由下往上順著響應(yīng)者鏈傳遞上去
4、接著就會(huì)調(diào)用上一個(gè)響應(yīng)者的touches...方法
5、如果上一個(gè)響應(yīng)者不能響應(yīng),則傳遞給上上一個(gè)響應(yīng)者
6、如果傳遞到UIApplication還是不能處理,那這個(gè)時(shí)間就會(huì)被廢棄
判斷上一個(gè)響應(yīng)者
- 如果當(dāng)前視圖是視圖控制器的view,則上一個(gè)響應(yīng)者是該視圖的視圖控制器
- 如果當(dāng)前視圖沒有視圖控制器,則上一個(gè)響應(yīng)者是該視圖的父視圖
實(shí)例

所屬關(guān)系:
self.view:子控件灰色
灰色:子控件先添加紅色,后添加綠色
綠色:子控件先添加橘色,后添加白色
符合:滿足可接收事件條件,自身能接收事件,觸摸點(diǎn)在自己身上
否:不能接收事件
點(diǎn)擊紅色:UIApplication -> UIWindow -> 灰色(符合)-> 查詢灰色子控件(紅色、綠色)-> 綠色(觸摸點(diǎn)不在身上,否)-> 紅色(符合)
點(diǎn)擊白色:UIApplication -> UIWindow -> 灰色(符合)-> 查詢灰色子控件(紅色、綠色)-> 綠色(符合)-> 查詢綠色子控件(白色、橘色)-> 橘色(觸摸點(diǎn)不在身上,否)-> 白色(符合)
點(diǎn)擊橘色:UIApplication -> UIWindow -> 灰色(符合)-> 查詢灰色子控件(紅色、綠色)-> 綠色(符合)-> 查詢綠色子控件(白色、橘色)-> 橘色(符合)
具體演示demo,請(qǐng)點(diǎn)擊這里。