iOS 事件包括:運(yùn)動(dòng)事件枚钓、遠(yuǎn)程控制事件电湘、觸摸事件。
其中觸摸事件的響應(yīng)流程是:當(dāng)手指觸摸屏幕時(shí)值纱,會(huì)產(chǎn)生一個(gè)事件,事件發(fā)生會(huì)被系統(tǒng)封裝為一個(gè) IOHIDEvent對(duì)象坯汤,傳遞給 SpringBoard(桌面)虐唠,然后將這個(gè)桌面進(jìn)程傳遞給當(dāng)前 app。app 的主線程 Runloop 會(huì)被觸發(fā)惰聂,因此會(huì)觸發(fā) Source1回調(diào)疆偿,Source1回調(diào)內(nèi)部觸發(fā) Source0 回調(diào),然后傳遞給 UIWindow庶近,app 內(nèi)部開(kāi)始傳遞和響應(yīng)翁脆。
事件傳遞與響應(yīng)
事件傳遞流程:通過(guò)hitTest
、pointInside
來(lái)反序遍歷找到正確的view鼻种。
從UIWindow開(kāi)始反番,反向遍歷 UIWindow 的 subviews。從最后一個(gè) subview 開(kāi)始,判斷點(diǎn)擊區(qū)域是不是在當(dāng)前 view 的 bounds 范圍內(nèi)罢缸。如果是(pointInside
返回 YES)篙贸,反向遍歷當(dāng)前 view 的 subviews,如果沒(méi)有 subviews枫疆,則當(dāng)前 view 是需要的 view爵川;如果不是(pointInside
返回 YES), 查找當(dāng)前 view 的兄弟 view息楔。這是一個(gè)遞歸查找過(guò)程寝贡。
pointInside:
- 如果返回 NO,則不會(huì)遍歷該 view 的 subviews值依;
- 觸摸的點(diǎn)不在范圍內(nèi)圃泡,找兄弟級(jí)別,在范圍內(nèi)愿险,找兒子級(jí)別颇蜡。
hitTest:
- 會(huì)調(diào)用
pointInside
; - 根據(jù)
pointInside
的值做不同的處理:如果pointInside
為 YES辆亏,則反序遍歷 subviews风秤;如果不是,返回 nil扮叨。 - 如果點(diǎn)擊區(qū)域在自己這里缤弦,而不是在子 view 里面,返回自己甫匹;
- 如果點(diǎn)擊區(qū)域在子 view 里甸鸟,返回子 view。
注意:如果 view 的alpha
小于等于0.01兵迅,或者 userInteractionEnabled
屬性為 NO抢韭,或者 hidden
為 YES 時(shí),view 不響應(yīng)事件恍箭。
事件響應(yīng):
每個(gè)view都有一個(gè)nextResponder對(duì)象刻恭,這樣就逐級(jí)串聯(lián)起一個(gè)響應(yīng)鏈。
有了響應(yīng)鏈扯夭,會(huì)進(jìn)行touch四個(gè)方法的傳遞響應(yīng)鳍贾;可以通過(guò)不寫(xiě) super touchBegin/moved/cancel/end 來(lái)阻止傳遞流程。
scrollView的 touch 事件傳遞不了交洗,因?yàn)橄到y(tǒng)會(huì)重寫(xiě) scrollView 的 super touch:
骑科,導(dǎo)致無(wú)法向上傳遞。
手勢(shì)
手勢(shì)和 pointInside 及 hitTest 的關(guān)系:
必須通過(guò) pointInside 和 hitTest 找到 view构拳,才能響應(yīng) view 的手勢(shì)事件咆爽;
通過(guò) pointInside 梁棠、 hitTest 找到的 view,其自身和它的 superview 的手勢(shì)都能響應(yīng)斗埂,但不會(huì)響應(yīng)其子 view 的手勢(shì)符糊;
手勢(shì)的種類(lèi)根據(jù)收拾自己的 touchs 的四個(gè)方向辨別;
默認(rèn)情況下呛凶,如果手勢(shì)識(shí)別出來(lái)了男娄,會(huì) cancel 掉 view 的 touch 事件。
修改
delaysContentTouches
canCancelContentTouches
的值可使手勢(shì)和 touch 共存漾稀。
scrollView.delaysContentTouches = NO; // 如果為 YES模闲,阻止 touch 方法的識(shí)別
scrollView.canCancelContentTouches = NO;
這兩個(gè)屬性可實(shí)現(xiàn)scrollView上的其他view滑動(dòng)時(shí),scrollView不滑動(dòng)
button 事件
- button 和 pointInside 及 hitTest 的關(guān)系:hitTest 必須有返回的 button县好,才能響應(yīng)事件围橡。
- button 不同事件的識(shí)別也是通過(guò)四個(gè) touch 方法分辨的。
- button 的
sendActionForControlEvents:
方法可以識(shí)別事件缕贡。
如果想讓 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];
}
如果一個(gè) button 超出了其父控件的范圍,要讓超出范圍的區(qū)域響應(yīng) button 的點(diǎn)擊贮配,則需要在其父類(lèi)的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];
}
手勢(shì)的互斥與共存
- 通過(guò)
UIGestureRecognizerDelegate
的代理方法操作
/**
手勢(shì)已經(jīng)識(shí)別,通過(guò)返回值確定是否響應(yīng)
*/
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
/**
兩個(gè)手勢(shì)是否共存泪勒,只要其中一個(gè)手勢(shì)的代理d方法返回 YES昼蛀,則兩個(gè)手勢(shì)共存
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return NO;
}
/**
gestureRecognizer 要響應(yīng),必須 otherGestureRecognizer 響應(yīng)失敗圆存,otherGestureRecognizer 優(yōu)先級(jí)高
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
/**
otherGestureRecognizer 要響應(yīng)叼旋,必須 gestureRecognizer 響應(yīng)失敗,gestureRecognizer 優(yōu)先級(jí)高
*/
//- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// return YES;
//}
/**
手勢(shì)是否支持 touch
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
- 讓其中一個(gè)手勢(shì)的優(yōu)先級(jí)變高
A requireGestureRecognizerToFail:B
(讓 B 優(yōu)先級(jí)更高)沦辙。
UITableView 的 didselect 方法是通過(guò) touch 起作用夫植,如果給 tableview 的父控件添加手勢(shì),則 tableview 的 didselect 方法不會(huì)實(shí)現(xiàn)油讯。原因是 tableview 的 touch 事件和父控件的手勢(shì)沖突详民,根本原因是手勢(shì)的 cancelsTouchesInView
的值為 YES。
cancelsTouchesInView
的作用是當(dāng)手勢(shì)識(shí)別出來(lái)時(shí)陌兑,會(huì)取消touch事件沈跨。
解決以上實(shí)例的途徑是將手勢(shì)的 cancelsTouchesInView
的值設(shè)為 NO。此時(shí)手勢(shì)和 tableview 的 touch 都會(huì)響應(yīng)兔综。
要 didselect 方法起作用而手勢(shì)不響應(yīng)饿凛,應(yīng)該在手勢(shì)的代理方法 shouldReceiveTouch 中判斷 touch 的 view 的種類(lèi)隅俘。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
UIView *view = touch.view;
return ![view isKindOfClass:NSClassFromString(@"UITableViewCellContentView")];
}