【问题标题】:What's the trick to pass an event to the next responder in the responder chain?将事件传递给响应者链中的下一个响应者的技巧是什么?
【发布时间】:2009-07-30 15:15:53
【问题描述】:

苹果真的很有趣。我的意思是,他们说这行得通:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [touches anyObject];
    NSUInteger numTaps = [touch tapCount];
    if (numTaps < 2) {
        [self.nextResponder touchesBegan:touches withEvent:event];
   } else {
        [self handleDoubleTap:touch];
   }
}

我有一个视图控制器。如你所知,视图控制器继承自 UIResponder。该视图控制器创建一个继承自 UIView 的 MyView 对象,并将其作为子视图添加到它自己的视图中。

所以我们有:

View Controller > 有一个 View(自动)> 有一个 MyView(这是一个 UIView)。

现在在 MyView 中,我将上面的代码与打印“touched MyView”的 NSLog 放在一起。但是我将事件转发给下一个响应者,就像上面一样。在 ViewController 内部我有另一个 touchesBegan 方法,它只打印一个 NSLog 一个 la “触摸视图控制器”。

现在猜猜看:当我触摸 MyView 时,它会打印“touched MyView”。当我触摸 MyView 外部时,即 VC 的视图,我得到一个“触摸视图控制器”。所以两者都有效!但不起作用的是转发事件。因为现在,实际上下一个响应者应该是视图控制器,因为中间没有别的东西。但是我转发时VC的事件处理方法从来没有被调用过。

%$&!§!!

想法?

找出奇怪的东西 MyView 的下一个响应者是视图控制器的视图。这是有道理的,因为 MyView 是它的子视图。但是我没有从视图控制器修改这个 UIView。这不是什么习俗。而且它没有实现任何触摸事件处理。消息不应该传递给视图控制器吗?我怎么能让它过去呢?如果我删除 MyView 中的事件处理代码,那么事件会很好地到达视图控制器。

【问题讨论】:

    标签: iphone


    【解决方案1】:

    根据similar question,您的方法应该有效。

    这使我认为您的视图的nextResponder 不是实际上是ViewController,正如您所怀疑的那样。

    我会在您的转发代码中添加一个快速的NSLog 以检查您的nextResponder 真正指向的内容:

    if (numTaps < 2) {
        NSLog(@"nextResponder = %@", self.nextResponder);
        [self.nextResponder touchesBegan:touches withEvent:event];
    }
    

    您还可以更改其他 NSLog 消息,以便它们输出类型和地址信息:

    NSLog(@"touched %@", self);
    

    touched &lt;UIView 0x12345678&gt;

    这应该可以帮助您开始诊断问题。

    【讨论】:

    • 谢谢eJames。 MyView 告诉我下一个响应者正是视图控制器的视图。内存地址匹配完美。但是,不会调用 this touchesBegan。但现在我看到这个方法将在视图控制器的视图上调用,而不是在视图控制器本身上。但是,这个事件不会继续冒泡到视图的视图控制器不处理它吗?还是这是另一种特殊情况,因为我是手动发送的?
    • 嗯,我想这是有道理的。 touchesBegan: 的默认实现什么都不做,所以一旦你将它传递给下一个 UIView 对象,它就会结束。简单地手动将触摸事件传递给 viewController 是否可行?
    • 您是否尝试过调用[super touchesBegan:...] 而不是[self.nextResponder touchesBegan:...] 我见过有人使用这种方法,但我不确定它的作用。
    • 这应该具有相同的效果,因为视图处于层次结构中。 super 不是控制器,而是控制器的视图。
    【解决方案2】:

    遇到了麻烦,因为我的自定义视图在视图层次结构中更深。相反,我爬上了响应者链,直到找到UIViewController

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        // Pass to top of chain
        UIResponder *responder = self;
        while (responder.nextResponder != nil){
            responder = responder.nextResponder;
            if ([responder isKindOfClass:[UIViewController class]]) {
                // Got ViewController
                break;
            }
        }
        [responder touchesBegan:touches withEvent:event];
    }
    

    【讨论】:

      【解决方案3】:

      如果您查看UIResponder 的文档,说明默认情况下:

      UIView 通过返回管理它的 UIViewController 对象(如果有)或其父视图(如果没有)来实现此方法;

      如果您的视图没有返回视图控制器,那么它一定会返回其父视图(如您所见)。

      UIApplication 实际上有一个方法可以准确地处理这个用例:

      - (BOOL)sendAction:(SEL)action
                      to:(id)target
                    from:(id)sender
                forEvent:(UIEvent *)event
      

      由于您没有对目标的引用,因此您可以通过将目标值设置为 nil 来沿着响应者链向上工作,因为根据文档:

      如果 target 为 nil,则应用程序将消息发送给第一个响应者,从那里沿着响应者链向上移动,直到它被处理。

      您当然可以通过其类方法[UIApplication sharedApplication] 访问 UIApplication

      更多信息在这里:UIApplication documents

      这是额外的,但是如果你绝对需要访问视图控制器来做一些更复杂的事情,你可以通过调用nextResponder 方法直到你到达视图控制器,直到它返回一个视图控制器。例如:

      UIResponder *responder = self;
      while ([responder isKindOfClass:[UIView class]])
          responder = [responder nextResponder];
      

      然后你可以将它作为你的视图控制器并继续做任何你想做的事情:

      UIViewController *parentVC = (UIViewController *)responder
      

      但请注意,这会破坏 MVC 模式,并且可能意味着您正在以“hacky”方式做某事

      【讨论】:

        【解决方案4】:

        是的,它应该 100% 在常见情况下工作。我刚刚尝试过测试项目,一切似乎都很好。

        我创建了基于视图的应用程序。使用 Interface Builder 将新的 UIView 放到当前的 View 上。然后使用 UIView 的子类创建新文件,并为我的新视图选择刚刚创建的类。 (接口生成器->类标识->类->MyViewClass)

        为 MyViewClass 和 UIViewController 添加触摸处理函数。

        //MyViewClass

        - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        NSLog(@"myview touches");
        [self.nextResponder touchesBegan:touches withEvent:event];
        }
        

        //视图控制器

        - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  
          NSLog(@"controller touches");
        }   
        

        按 MyViewClass 时,我看到两个 NSLog。在加载 ViewController 或使用 loadView 函数以编程方式设置视图时,您是否使用了 Interface Builder 和 XIB 文件?

        【讨论】:

          【解决方案5】:

          要引用视图控制器,您需要使用

          [[self nextResponder] nextResponder]
          

          因为[self nextResponder] 表示视图控制器的视图, 和[[self nextResponder] nextResponder] 表示视图控制器本身,现在您可以获取触摸事件

          【讨论】:

          • 可能在 iOS9 和 10 中运行良好,但在 iOS11 中不运行。你真的需要爬上响应者链,直到你到达你所追求的 viewController 或 tableViewController。
          【解决方案6】:

          我知道这篇文章很旧,但我认为 id share 因为我有类似的经历。我通过将自动创建的视图控制器的视图的 userInteractionEnabled 设置为 NO 来修复它。

          【讨论】:

            【解决方案7】:

            今天我找到了一个更好的方法来解决这个问题或类似的问题。

            在你的 UITableViewCell 中,你可以添加一个代理来监听触摸事件,像这样:

            @protocol imageViewTouchDelegate
            
            -(void)selectedFacialView:(NSInteger)row item:(NSInteger)rowIndex;
            
            @end
            
            @property(nonatomic,assign)id<imageViewTouchDelegate>delegate;
            

            然后在你的 UITableViewController 中:你可以这样做:

            @interface pressionTable : UITableViewController<facialViewDelegate>
            

            在 .m 文件中,您可以实现这个委托接口,例如:

            -(void)selectedFacialView:(NSInteger)row item:(NSInteger)rowIndex{
            //TO DO WHAT YOU WANT}
            

            注意:初始化单元格时,必须设置委托,否则委托无效,可以这样做

            - (UITableViewCell *)tableView:(UITableView *)tableView 
                 cellForRowAtIndexPath:(NSIndexPath *)indexPath {
            
            static NSString *CellIdentifier = @"Cell";
            
            pressionCell *cell = (pressionCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
            if (cell == nil) {
                cell = [[pressionCell alloc]initWithFrame:CGRectMake(0, 0, 240, 40)];
            }
            
            cell.delegate = self;
            

            }

            PS:我知道这个 DOC 很旧,但我仍然添加我的方法来解决问题,所以当人们遇到同样的问题时,他们会从我的回答中得到帮助。

            【讨论】:

              【解决方案8】:

              使用这个

                  [super.nextResponder touchesBegan:touches withEvent:event];
              

              代码中这一行之前

                  [self.nextResponder touchesBegan:touches withEvent:event];
              

              意思是

              - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
                   UITouch* touch = [touches anyObject];
                   NSUInteger numTaps = [touch tapCount];
                   if (numTaps < 2) {
                       [super.nextResponder touchesBegan:touches withEvent:event];
                       [self.nextResponder touchesBegan:touches withEvent:event];
                   } else {
                       [self handleDoubleTap:touch];
                   }
              }
              

              **Swift 4 版本 **

              func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
                  let touch = touches?.first as? UITouch
                  let numTaps: Int? = touch?.tapCount
                  if (numTaps ?? 0) < 2 {
                      if let aTouches = touches as? Set<UITouch> {
                          super.next?.touchesBegan(aTouches, with: event)
                          next?.touchesBegan(aTouches, with: event)
                      }
                  } else {
                      handleDoubleTap(touch)
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-10-08
                • 2021-11-26
                • 2020-05-15
                • 2011-06-10
                • 1970-01-01
                • 2011-04-21
                相关资源
                最近更新 更多