1. iOS事件有哪一些
運(yùn)動(dòng)事件
遠(yuǎn)程控制事件
觸摸事件
2. 事件傳遞和響應(yīng)
2.1 原理分析
- 當(dāng)我們手指觸碰到屏幕的時(shí)候猖腕,事件傳遞和響應(yīng)的流程是怎么樣的呢
- 事件的流程圖
- IOKit.framework 為系統(tǒng)內(nèi)核的庫(kù)
- SpringBoard.app 相當(dāng)于手機(jī)的桌面
- Source1 主要接收系統(tǒng)的消息
- Source0 - UIApplication - UIWindow
- 從UIWindow 開始步驟,見下圖
- 比如我們?cè)趕elf.view 上依次添加view1恨闪、view2倘感、view3(3個(gè)view是同級(jí)關(guān)系),那么系統(tǒng)用
hitTest
以及pointInside
時(shí)會(huì)先從view3開始便利咙咽,如果pointInside
返回YES
就繼續(xù)遍歷view3的subviews(如果view3沒有子視圖老玛,那么會(huì)返回view3),如果pointInside
返回NO
就開始便利view2钧敞。
- 反序遍歷蜡豹,最后一個(gè)添加的subview開始。也算是一種算法優(yōu)化
2.2 HitTest 溉苛、pointInside
- 上一段層級(jí)關(guān)系的簡(jiǎn)單示例代碼
EOCLightGrayView *grayView = [[EOCLightGrayView alloc] initWithFrame:CGRectMake(50.f, 100.f, 260.f, 200.f)];
redView = [[EOCRedView alloc] initWithFrame:CGRectMake(0.f, 0.f, 120.f, 100.f)];
EOCBlueView *blueView = [[EOCBlueView alloc] initWithFrame:CGRectMake(140.f, 100.f, 100.f, 100.f)];
EOCYellowView *yellowView = [[EOCYellowView alloc] initWithFrame:CGRectMake(50.f, 360.f, 200.f, 200.f)];
[self.view addSubview:grayView];
[grayView addSubview:redView];
[grayView addSubview:blueView];
[self.view addSubview:yellowView];
- 點(diǎn)擊
red
镜廉,由于yellow
與 grey
同級(jí),yellow
比 grey
后添加愚战,所以先打印yellow
娇唯,由于觸摸點(diǎn)不在yellow
內(nèi),打印grey
寂玲,然后遍歷grey
塔插,打印他的兩個(gè)subviews
- 通過在
HitTest
返回nil
,pointInside
并沒有執(zhí)行拓哟,我們可以得知想许,pointInside
調(diào)用順序你在HitTest
之后的。
-
pointInside
的 參數(shù) :(CGPoint)poinit
的值是以自身為坐標(biāo)系的,判斷點(diǎn)是否view內(nèi)的范圍是以view自身的bounds
為范圍流纹,而非frame
- 如果在
grey
的hitTest
返回[super hitTest:point event:event]
谎砾,則會(huì)執(zhí)行gery.subviews
的遍歷(subviews 的 hitTest 與 pointInside)
,grey
的 pointInside
是判斷觸摸點(diǎn)是否在grey
的bounds
內(nèi)(不準(zhǔn)確)捧颅,grey
的 hitTest
是判斷是否需要遍歷他的subviews
.
-
pointInside
只是在執(zhí)行hitTest
時(shí)景图,會(huì)在hitTest
內(nèi)部調(diào)用的一個(gè)方法
-
pointInside
只是輔助hitTest
的關(guān)系
-
hitTest
是一個(gè)遞歸函數(shù)
2.3 hitTest 內(nèi)部實(shí)現(xiàn)代碼還原
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
///hitTest:判斷pointInside,是不是在view里碉哑?是的話挚币,遍歷,不是的話返回nil;假設(shè)我就是點(diǎn)擊灰色的扣典,返回的是自己妆毕;
NSLog(@"%s",__func__);
NSArray *subViews = [[self.subviews reverseObjectEnumerator] allObjects];
UIView *tmpView = nil;
for (UIView *view in subViews) {
CGPoint convertedPoint = [self convertPoint:point toView:view];
if ([view pointInside:convertedPoint withEvent:event]) {
tmpView = view;
break;
}
}
if (tmpView) {
return tmpView;
} else if([self pointInside:point withEvent:event]) {
return self;
} else {
return nil;
}
return [self hitTest:point event:event]; //redView
///這里是hitTest的邏輯
///alpha(<=0.01)、userInterActionEnabled(NO)贮尖、hidden(YES) pointInside返回的為NO
}
2.4 實(shí)戰(zhàn)之?dāng)U大button點(diǎn)擊區(qū)域
- 紅色為button
- 藍(lán)色為放大后的目標(biāo)點(diǎn)擊區(qū)域
- 稍微注意是在bounds的基礎(chǔ)上修改
- button 內(nèi)部的
hitTest
通過 pointInside
的確認(rèn)笛粘,來決定是否返回自己
@implementation EOCCustomButton
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(@"%s", __func__);
//擴(kuò)大它的響應(yīng)范圍
CGRect frame = [self getScaleFrame];
return CGRectContainsPoint(frame, point);
// return [super pointInside:point withEvent:event];
}
- (CGRect)getScaleFrame {
CGRect rect = self.bounds;
if (rect.size.width < 40.f) {
rect.origin.x -= (40-rect.size.width)/2;
}
if (rect.size.height < 40.f) {
rect.origin.y -= (40-rect.size.height)/2;
}
rect.size.width = 40.f;
rect.size.height = 40.f;
return rect;
}
2.5 UIRespond 與 響應(yīng)鏈的組成
- 當(dāng)我們通過
hitTest
找到視圖后,我們產(chǎn)生的touch事件湿硝,他是怎么一層層響應(yīng)的薪前?
- 響應(yīng)鏈?zhǔn)峭ㄟ^
nextResponder
屬性組成的一個(gè)鏈表
- 點(diǎn)擊的 view 有 superView ,nextResponder 就是 superView ;
-
view.nextResponder .nextResponder
是viewController 或者是 view.superView. view
-
view.nextResponder .nextResponder. nextResponder
是 UIWindow (非嚴(yán)謹(jǐn),便于理解)
-
view.nextResponder .nextResponder. nextResponder. nextResponder
是UIApplication 、UIAppdelate关斜、直到nil (非嚴(yán)謹(jǐn),便于理解)
-
touch
事件就是根據(jù)響應(yīng)鏈的關(guān)系來層層調(diào)用(我們重寫touch 要記得 super
調(diào)用示括,不然響應(yīng)鏈會(huì)中斷)
- 比如我們監(jiān)聽
self.view
的touch
事件,也是因?yàn)?code>subviews的touch
都在同一個(gè)響應(yīng)鏈里
3. 手勢(shì)事件
3.1 手勢(shì) 與 hitTest 的關(guān)系
- 相同上面的學(xué)習(xí)我們可以推測(cè)出痢畜,手勢(shì)的響應(yīng)也得必須經(jīng)過
hitTest
先找到視圖才能觸發(fā)(已驗(yàn)證)
3.2 手勢(shì)與 觸摸事件的關(guān)系
-
touch
事件是UIView
內(nèi)部的東西垛膝,而手勢(shì)疊加上去的觸摸事件
-
subview
會(huì)響應(yīng)superview
的手勢(shì), 但是同級(jí)的subview
不會(huì)響應(yīng)
3.3 系統(tǒng)如何分辨手勢(shì)種類
- 首先我們想在手勢(shì)中調(diào)用
touches
方法必須要導(dǎo)入
#import <UIKit/UIGestureRecognizerSubclass.h>
因?yàn)?code>gesture繼承的是NSObject
而不是 UIRespond
- 通過嘗試不調(diào)用 tap手勢(shì) 的
touchesBegan
丁稀,發(fā)現(xiàn)tap手勢(shì)無法響應(yīng)
- 通過嘗試調(diào)用
touchesBegan
吼拥,但是不調(diào)用 pan手勢(shì) 的touchesMoved
,發(fā)現(xiàn)pan手勢(shì)無法響應(yīng)
- 我們通過
UITouch
的實(shí)例线衫,可以看到里面有很多屬性凿可,比如點(diǎn)擊的次數(shù),上次的位置等桶雀,結(jié)合這個(gè)屬性系統(tǒng)與touches
方法就可以判斷出你使用的是什么手勢(shì)
3.4 手勢(shì)與view的touches事件的關(guān)系
- 首先通過觸摸事件矿酵,先響應(yīng)
touchesBegan
以及 touchesMoved
,直到手勢(shì)被識(shí)別出來矗积,調(diào)用touchesCancelled
全肮,全權(quán)交給手勢(shì)處理。
- 但是我們可以改變這種關(guān)系
下面是系統(tǒng)的默認(rèn)設(shè)置
tapGesture.delaysTouchesBegan = NO;
///是否延遲view的touch事件識(shí)別棘捣;如果延遲了(YES)辜腺,并且手勢(shì)也識(shí)別到了,touch事件會(huì)被拋棄
tapGesture.cancelsTouchesInView = YES;
///識(shí)別手勢(shì)之后,是否取消view的touch事件
// 如果為NO, touchesCancelled 不會(huì)調(diào)用评疗,取而代之的是手勢(shì)結(jié)束后touchesEnd
4. button事件
4.1 系統(tǒng)是如何分辨UIControlEvent
- 我們還是通過
button
內(nèi)部的touches
來實(shí)踐
- 實(shí)踐過程略测砂,與手勢(shì)同理
- 比如說
touchUpInside
,通過查看堆棧調(diào)用百匆,我們發(fā)現(xiàn)在touchesEnd
后完成對(duì)touchUpInside
的識(shí)別砌些,然后再調(diào)起sendAction:
方法
5. 觸摸事件的運(yùn)用
- 上文已經(jīng)講過一個(gè)button點(diǎn)擊范圍擴(kuò)大的案例,再講一個(gè)案例
- 與上個(gè)例子不同的是加匈,當(dāng)我們點(diǎn)擊
黑色
的時(shí)候存璃,因?yàn)樵?code>greyview的外面,別說響應(yīng)黑色button
了雕拼,我們直接不會(huì)響應(yīng)greyview
了纵东,怎么辦?
- 一種是在
self.view
的 -pointInside
返回 YES, 不過這種在交互復(fù)雜的場(chǎng)景不存在實(shí)用性
- 我們可以重寫
self.view
的 -hitTest
, 把當(dāng)前觸摸的點(diǎn)分別轉(zhuǎn)化為subviews
上的坐標(biāo)系的點(diǎn)啥寇,在用subviews
的 pointinside
判斷此點(diǎn)偎球,然后返回對(duì)應(yīng)的subviews
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者