将事件传递给响应者链中的下一个响应者的诀窍是什么?

时间:2009-07-30 15:15:54

标签: iphone

Apple非常有趣。我的意思是,他们说这有效:

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

我有一个View Controller。如您所知,View Controllers继承自UIResponder。 View Controller创建一个继承自UIView的MyView对象,并将其作为子视图添加到它自己的视图中。

所以我们有:

查看控制器&gt;有一个视图(自动)&gt;有一个MyView(这是一个UIView)。

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

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

%$&安培;!§!!

想法?

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

8 个答案:

答案 0 :(得分:5)

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

这让我认为您的观点nextResponder 实际上不是<{1}},正如您所怀疑的那样。

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

nextResponder

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

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

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

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

答案 1 :(得分:4)

这有问题,因为我的自定义视图在视图层次结构中更深。相反,我爬上了响应链,直到找到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];
}

答案 2 :(得分:2)

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

我创建了基于视图的应用程序。使用Interface Builder将新的UIView放到当前View中。然后使用UIView的子类创建新文件,并为我的新视图选择刚创建的类。 (Interface Builder-&gt; Class identity-&gt; Class-&gt; MyViewClass)

为MyViewClass和UIViewController添加touches处理函数。

// MyViewClass

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

// ViewController

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

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

答案 3 :(得分:2)

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

[[self nextResponder] nextResponder]

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

答案 4 :(得分:2)

如果查看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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

在您的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已经老了,但是我仍然添加我的方法来修复问题,所以当人们遇到同样的问题时,他们会从我的答案中得到帮助。

答案 7 :(得分:1)

使用此

    [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)
    }
}