前言
當(dāng)用戶點(diǎn)擊付款跳轉(zhuǎn)到付款界面、點(diǎn)擊掃一掃進(jìn)入掃描二維碼視圖缚态。
當(dāng)我們點(diǎn)擊屏幕的時(shí)候喊儡,這個(gè)點(diǎn)擊事件由硬件層傳向iPhone OS(操作系統(tǒng)),
然后操作系統(tǒng)把這些信息包裝成UITouch(點(diǎn)擊對象)和UIEvent(事件對象)哩治,然后找到當(dāng)前運(yùn)行的程序秃踩,
逐級尋找能夠響應(yīng)這次事件的響應(yīng)者,這一尋找過程稱為事件的響應(yīng)鏈业筏。
過程:AppDelegate -> UIApplication -> UIWindow ->UIController(UIResponder子類)-UIView->subViews
- UITouch
UITouch
表示單個(gè)點(diǎn)擊憔杨,其類文件中存在枚舉類型UITouchPhase
的屬性,用來表示當(dāng)前點(diǎn)擊的狀態(tài)蒜胖。
這些狀態(tài)包括點(diǎn)擊開始消别、移動(dòng)、停止不動(dòng)台谢、結(jié)束和取消五個(gè)狀態(tài)寻狂。
每次點(diǎn)擊發(fā)生的時(shí)候,點(diǎn)擊對象都放在一個(gè)集合中傳入UIResponder
的回調(diào)方法中朋沮。
我們通過集合中對象獲取用戶點(diǎn)擊的位置蛇券,其中通過- (CGPoint)locationInView:(nullable UIView *)view
獲取當(dāng)前點(diǎn)擊坐標(biāo)點(diǎn),
- (CGPoint)previousLocationInView:(nullable UIView *)view
獲取上個(gè)點(diǎn)擊位置的坐標(biāo)點(diǎn)樊拓。 - UIEvent
iOS使用UIEvent
表示用戶交互的事件對象纠亚,在UIEvent.h
文件中,我們可以看到有一個(gè)UIEventType
類型的屬性;
這個(gè)屬性表示了當(dāng)前的響應(yīng)事件類型;分別有多點(diǎn)觸控筋夏、搖一搖以及新增的3DTouch
事件類型蒂胞。
在一個(gè)用戶點(diǎn)擊事件處理過程中,UIEvent
對象是唯一的条篷。
Hit-test View
決定誰是Hit-test View通過不斷遞歸- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
和
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
以下兩個(gè)函數(shù)實(shí)現(xiàn)的:
// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
// default returns YES if point is in bounds
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Hit-test檢查機(jī)制
- 通過調(diào)用自身
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
如果返回YES骗随,則繼續(xù)遍歷View中subViews直到?jīng)]有subViews為止蛤织。 - 如果當(dāng)前View.userInteractionEnabled = NO,enabled=NO(UIControl),或者alpha<=0.01, View Hidden等情況的時(shí)候,hitTest就不會(huì)調(diào)用自己的
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
了鸿染,直接返回nil钳垮,然后系統(tǒng)去遍歷其他子節(jié)點(diǎn)踏枣。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.alpha <= 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;
}
}
事件分發(fā)處理
- 當(dāng)找到Hit-test View之后,事件分發(fā)正式開始,如果Hit-test View能處理就直接處理逻族,如果不能處理温技,就交給 The Responder Chain或者
GestureRecognizer處理茁彭。 - 獲取可處理事件對象后調(diào)用這些對象
touches
回調(diào)方法国瓮。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- 如果是
UIGestureRecognizer
事件,事件分發(fā)又不一樣播瞳,經(jīng)過- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
后走的是UIGestureRecognizerDelegate
回調(diào)掸刊。 - 當(dāng)沒有響應(yīng)者,事件到
AppDelegate
后依舊未能響應(yīng)赢乓,系統(tǒng)會(huì)放棄此次事件忧侧。
實(shí)際運(yùn)用
- 為某個(gè)button擴(kuò)大點(diǎn)擊區(qū)域,即使不在button區(qū)域也能響應(yīng)button事件牌芋。
重載UIButton的-(BOOL)pointInside: withEvent:
方法蚓炬,讓Point即使落在Button的Frame外圍也返回YES。
//in custom button .m
//overide this method
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
return CGRectContainsPoint(HitTestingBounds(self.bounds, self.minimumHitTestWidth, self.minimumHitTestHeight), point);
}
CGRect HitTestingBounds(CGRect bounds, CGFloat minimumHitTestWidth, CGFloat minimumHitTestHeight) {
CGRect hitTestingBounds = bounds;
if (minimumHitTestWidth > bounds.size.width) {
hitTestingBounds.size.width = minimumHitTestWidth;
hitTestingBounds.origin.x -= (hitTestingBounds.size.width - bounds.size.width)/2;
}
if (minimumHitTestHeight > bounds.size.height) {
hitTestingBounds.size.height = minimumHitTestHeight;
hitTestingBounds.origin.y -= (hitTestingBounds.size.height - bounds.size.height)/2;
}
return hitTestingBounds;
}
不規(guī)則形狀點(diǎn)擊事件處理躺屁。
重寫-(BOOL)pointInside: withEvent:
和touches
回調(diào)處理