iOS 事件包括:運動事件、遠程控制事件、觸摸事件。
其中觸摸事件的響應(yīng)流程是:當手指觸摸屏幕時,會產(chǎn)生一個事件,事件發(fā)生會被系統(tǒng)封裝為一個 IOHIDEvent對象,傳遞給 SpringBoard(桌面),然后將這個桌面進程傳遞給當前 app。app 的主線程 Runloop 會被觸發(fā),因此會觸發(fā) Source1回調(diào),Source1回調(diào)內(nèi)部觸發(fā) Source0 回調(diào),然后傳遞給 UIWindow,app 內(nèi)部開始傳遞和響應(yīng)。
事件傳遞與響應(yīng)
事件傳遞流程:通過hitTest
、pointInside
來反序遍歷找到正確的view。
從UIWindow開始,反向遍歷 UIWindow 的 subviews。從最后一個 subview 開始,判斷點擊區(qū)域是不是在當前 view 的 bounds 范圍內(nèi)。如果是(pointInside
返回 YES),反向遍歷當前 view 的 subviews,如果沒有 subviews,則當前 view 是需要的 view;如果不是(pointInside
返回 YES), 查找當前 view 的兄弟 view。這是一個遞歸查找過程。
pointInside:
- 如果返回 NO,則不會遍歷該 view 的 subviews;
- 觸摸的點不在范圍內(nèi),找兄弟級別,在范圍內(nèi),找兒子級別。
hitTest:
- 會調(diào)用
pointInside
; - 根據(jù)
pointInside
的值做不同的處理:如果pointInside
為 YES,則反序遍歷 subviews;如果不是,返回 nil。 - 如果點擊區(qū)域在自己這里,而不是在子 view 里面,返回自己;
- 如果點擊區(qū)域在子 view 里,返回子 view。
注意:如果 view 的alpha
小于等于0.01,或者 userInteractionEnabled
屬性為 NO,或者 hidden
為 YES 時,view 不響應(yīng)事件。
事件響應(yīng):
每個view都有一個nextResponder對象,這樣就逐級串聯(lián)起一個響應(yīng)鏈。
有了響應(yīng)鏈,會進行touch四個方法的傳遞響應(yīng);可以通過不寫 super touchBegin/moved/cancel/end 來阻止傳遞流程。
scrollView的 touch 事件傳遞不了,因為系統(tǒng)會重寫 scrollView 的 super touch:
,導(dǎo)致無法向上傳遞。
手勢
手勢和 pointInside 及 hitTest 的關(guān)系:
必須通過 pointInside 和 hitTest 找到 view,才能響應(yīng) view 的手勢事件;
通過 pointInside 、 hitTest 找到的 view,其自身和它的 superview 的手勢都能響應(yīng),但不會響應(yīng)其子 view 的手勢;
手勢的種類根據(jù)收拾自己的 touchs 的四個方向辨別;
默認情況下,如果手勢識別出來了,會 cancel 掉 view 的 touch 事件。
修改
delaysContentTouches
canCancelContentTouches
的值可使手勢和 touch 共存。
scrollView.delaysContentTouches = NO; // 如果為 YES,阻止 touch 方法的識別
scrollView.canCancelContentTouches = NO;
這兩個屬性可實現(xiàn)scrollView上的其他view滑動時,scrollView不滑動
button 事件
- button 和 pointInside 及 hitTest 的關(guān)系:hitTest 必須有返回的 button,才能響應(yīng)事件。
- button 不同事件的識別也是通過四個 touch 方法分辨的。
- button 的
sendActionForControlEvents:
方法可以識別事件。
如果想讓 button 的響應(yīng)區(qū)域變大,只需要在想要的區(qū)域里,pointInside 返回為 YES 就可以。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGRect rect = self.bounds;
if (rect.size.width < 50.f) {
rect.origin.x -= (50.f - rect.size.width) / 2;
}
if (rect.size.height < 50.f) {
rect.origin.y -= (50.f - rect.size.height) / 2;
}
rect.size.width = 50.f;
rect.size.height = 50.f;
if (CGRectContainsPoint(rect, point)) {
return YES;
}
return [super pointInside:point withEvent:event];
}
如果一個 button 超出了其父控件的范圍,要讓超出范圍的區(qū)域響應(yīng) button 的點擊,則需要在其父類的pointInside
方法里判斷。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
UIView *subview = self.subviews[0];
CGPoint convertedPoint = [self convertPoint:point toView:subview];
if ([subview pointInside:convertedPoint withEvent:event]) {
return YES;
}
return [super pointInside:point withEvent:event];
}
手勢的互斥與共存
- 通過
UIGestureRecognizerDelegate
的代理方法操作
/**
手勢已經(jīng)識別,通過返回值確定是否響應(yīng)
*/
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
/**
兩個手勢是否共存,只要其中一個手勢的代理d方法返回 YES,則兩個手勢共存
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return NO;
}
/**
gestureRecognizer 要響應(yīng),必須 otherGestureRecognizer 響應(yīng)失敗,otherGestureRecognizer 優(yōu)先級高
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
/**
otherGestureRecognizer 要響應(yīng),必須 gestureRecognizer 響應(yīng)失敗,gestureRecognizer 優(yōu)先級高
*/
//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// return YES;
//}
/**
手勢是否支持 touch
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
- 讓其中一個手勢的優(yōu)先級變高
A requireGestureRecognizerToFail:B
(讓 B 優(yōu)先級更高)。
UITableView 的 didselect 方法是通過 touch 起作用,如果給 tableview 的父控件添加手勢,則 tableview 的 didselect 方法不會實現(xiàn)。原因是 tableview 的 touch 事件和父控件的手勢沖突,根本原因是手勢的 cancelsTouchesInView
的值為 YES。
cancelsTouchesInView
的作用是當手勢識別出來時,會取消touch事件。
解決以上實例的途徑是將手勢的 cancelsTouchesInView
的值設(shè)為 NO。此時手勢和 tableview 的 touch 都會響應(yīng)。
要 didselect 方法起作用而手勢不響應(yīng),應(yīng)該在手勢的代理方法 shouldReceiveTouch 中判斷 touch 的 view 的種類。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
UIView *view = touch.view;
return ![view isKindOfClass:NSClassFromString(@"UITableViewCellContentView")];
}