stack overflow上的有人提出responder chain問題UITableViewCell skipped in responder chain摘盆,并附上其git demo驗證responder chain趴酣;我在閱讀代碼后,在此寫下幾點體會
demo分析
代碼構(gòu)造的view層次結(jié)構(gòu)如下:
cell中button點擊操作代碼:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableCell *cell = [[TableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
ContentView *view = [[ContentView alloc] initWithFrame:cell.bounds];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.backgroundColor = [UIColor greenColor];
view.backgroundColor = [UIColor clearColor];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 44)];
[button setTitle:@"Button" forState:UIControlStateNormal];
button.backgroundColor = [UIColor blueColor];
[button addTarget:nil action:@selector(customEventFired:) forControlEvents:(1 << 24)];
[button addTarget:self action:@selector(sendStuff:) forControlEvents:UIControlEventTouchUpInside];
[view addSubview:button];
[cell.contentView addSubview:view];
return cell;
}
- (void)sendStuff:(id)sender {
UIButton *btn = (UIButton *)sender;
[btn sendActionsForControlEvents:(1 << 24)];
}
點擊按鈕時瓦呼,會通過hitTest來查找touch事件是在哪個view上,之后觸發(fā)類中的方法sendStuff:
测暗,該方法中又會觸發(fā)controlEvent(1<<24)
央串,因為[button addTarget:nil action:@selector(customEventFired:) forControlEvents:(1 << 24)];
磨澡,target是nil,會根據(jù)responder chain來查找可以處理事件的responder质和;接下來先介紹hitTest過程稳摄。
ios事件分成三類:
- 觸摸事件(Touch event):觸摸事件會被分發(fā)給觸摸產(chǎn)生的view,查找這個view的過程就被成為 hitTest
- 運動事件(Motion event):會被分發(fā)給first responder處理
- 遠程事件 (remote control event):同上
hitTest:withEvent:
touch event產(chǎn)生后饲宿,會被加入到app的事件隊列厦酬,按照先進先出的原則,依次取出事件瘫想,系統(tǒng)先發(fā)給主窗口仗阅,主窗口按照view層次結(jié)構(gòu),去查找最小的發(fā)生觸摸事件的view国夜;主要過程如下:
- 判斷view是否接收touch event减噪,以下三種情況不接收:
- userInteractionEnabled為no
- hidden為YES
- alpha為0
- 觸摸點是否在view范圍中
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint btnPoint = [self convertPoint:point toView:_btn];
if ([_btn pointInsider:btnPoint withEvent:event])
{
return YES;
}
else
{
return [super pointInside:point withEvent:event];
}
}
- 依照上述過程遍歷子控件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint btnPoint = [_btn convertPoint:point fromView:self];
if ([_btn pointInside:btnPoint withEvent:event])
{
return _btn;
}
return [super hitTest:point withEvent:event];
}
- 如果沒有符合的子控件,則自身就是hitTest-view
在項目中车吹,運行結(jié)果調(diào)用旋廷,會發(fā)現(xiàn)
上述結(jié)果中沒有展示button的hitTest:withEvent:結(jié)果,其中{{0,0},{100,44}}是button的frame
注意clipsToBounds設(shè)置為NO礼搁,因為subview可以超出superView饶碘,所以這個時候要重寫
hitTest:withEvent:
或者pointInside:withEvent:
responder chain
hitTest view會處理touch event,但是如果其不能處理馒吴;則會根據(jù)響應(yīng)鏈從firstResponder開始往上傳遞扎运,尋找可以處理的responder為止。
根據(jù)responder chain確認event handler
first responder
被指派第一個接收事件饮戳,可以通過重寫
- (BOOL)canBecomeFirstResponder {
NSLog(@"canBecomeFirstResponder");
return YES;
}
再調(diào)用[cell becomeFirstResponder]
豪治,可以成為first responder;例如tableCell中重寫canBecomeFirstResponder
扯罐,并調(diào)用becomeFirstResponder
负拟,則在點擊button時,響應(yīng)鏈并沒有調(diào)用contentView中的customEventFired:
方法歹河,而是調(diào)用tableCell中的customEventFired:
方法掩浙。
參考
responder object
Event Delivery:The Responder Chain
iOS事件分發(fā)機制(一) hit-Testing
iOS事件分發(fā)機制(二)The Responder Chain