responder chain

stack overflow上的有人提出responder chain問(wèn)題UITableViewCell skipped in responder chain,并附上其git demo驗(yàn)證responder chain;我在閱讀代碼后,在此寫(xiě)下幾點(diǎn)體會(huì)

demo分析

代碼構(gòu)造的view層次結(jié)構(gòu)如下:

view tree.jpg

cell中button點(diǎn)擊操作代碼:

- (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)];
}

點(diǎn)擊按鈕時(shí),會(huì)通過(guò)hitTest來(lái)查找touch事件是在哪個(gè)view上,之后觸發(fā)類中的方法sendStuff:,該方法中又會(huì)觸發(fā)controlEvent(1<<24),因?yàn)?code>[button addTarget:nil action:@selector(customEventFired:) forControlEvents:(1 << 24)];,target是nil,會(huì)根據(jù)responder chain來(lái)查找可以處理事件的responder;接下來(lái)先介紹hitTest過(guò)程。

ios事件分成三類:

  1. 觸摸事件(Touch event):觸摸事件會(huì)被分發(fā)給觸摸產(chǎn)生的view,查找這個(gè)view的過(guò)程就被成為 hitTest
  2. 運(yùn)動(dòng)事件(Motion event):會(huì)被分發(fā)給first responder處理
  3. 遠(yuǎn)程事件 (remote control event):同上

hitTest:withEvent:

touch event產(chǎn)生后,會(huì)被加入到app的事件隊(duì)列,按照先進(jìn)先出的原則,依次取出事件,系統(tǒng)先發(fā)給主窗口,主窗口按照view層次結(jié)構(gòu),去查找最小的發(fā)生觸摸事件的view;主要過(guò)程如下:

  1. 判斷view是否接收touch event,以下三種情況不接收:
  • userInteractionEnabled為no
  • hidden為YES
  • alpha為0
  1. 觸摸點(diǎn)是否在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];
  }
}
  1. 依照上述過(guò)程遍歷子控件
- (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];
}
  1. 如果沒(méi)有符合的子控件,則自身就是hitTest-view

在項(xiàng)目中,運(yùn)行結(jié)果調(diào)用,會(huì)發(fā)現(xiàn)

屏幕快照 2016-07-31 下午4.27.41.png

上述結(jié)果中沒(méi)有展示button的hitTest:withEvent:結(jié)果,其中{{0,0},{100,44}}是button的frame

注意clipsToBounds設(shè)置為NO,因?yàn)閟ubview可以超出superView,所以這個(gè)時(shí)候要重寫(xiě)hitTest:withEvent:或者pointInside:withEvent:

responder chain

hitTest view會(huì)處理touch event,但是如果其不能處理;則會(huì)根據(jù)響應(yīng)鏈從firstResponder開(kāi)始往上傳遞,尋找可以處理的responder為止。

根據(jù)responder chain確認(rèn)event handler

responder chain.jpg

first responder

被指派第一個(gè)接收事件,可以通過(guò)重寫(xiě)

- (BOOL)canBecomeFirstResponder {
    NSLog(@"canBecomeFirstResponder");
    return YES;
}

再調(diào)用[cell becomeFirstResponder],可以成為first responder;例如tableCell中重寫(xiě)canBecomeFirstResponder,并調(diào)用becomeFirstResponder,則在點(diǎn)擊button時(shí),響應(yīng)鏈并沒(méi)有調(diào)用contentView中的customEventFired:方法,而是調(diào)用tableCell中的customEventFired:方法。

參考

responder object
Event Delivery:The Responder Chain
iOS事件分發(fā)機(jī)制(一) hit-Testing
iOS事件分發(fā)機(jī)制(二)The Responder Chain

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容