走响应链以传递自定义事件。这是错的吗?

时间:2010-10-05 12:50:59

标签: iphone ios cocoa events

根据iOS文档,响应者链用于“向上传递”触摸事件。它也用于控件生成的操作。细

我真正想做的是在链上发送自定义事件。接收事件的第一响应者将处理它。这似乎是一种非常常见的模式,但我找不到任何关于如何使用“iOS / Cocoa方式”的好解释。

由于响应者链正是我所需要的,我想出了这样的解决方案:

// some event happened in my view that 
// I want to turn into a custom event and pass it "up":

UIResponder *responder = [self nextResponder];

while (responder) {

   if ([responder conformsToProtocol:@protocol(ItemSelectedDelegate)]) {
       [responder itemSelected:someItem];
       break;
   } 

   responder = [responder nextResponder];
}

这很有效,但我觉得应该有其他方法来解决这个问题。这种方式手动走链似乎不太好......

请注意,通知在这里不是一个好的解决方案,因为我只希望参与视图层次结构中的对象,并且通知是全局的。

在iOS中处理这个问题的最佳方式是什么(和Cocoa一样)?

修改

我想要完成什么?

我有一个视图控制器,它有一个视图,它有子视图等...几个子视图是一个特定类型,显示数据库中的项目。当用户点击此视图时,应将信号发送到控制器以导航到此项目的详细信息页面。

处理点按的视图位于视图层次结构中主视图下方的几个级别。我必须告诉控制器(或者在某些情况下,特定的子视图“在链上”)选择了一个项目。

听取通知是一种选择,但我不喜欢这种解决方案,因为选择一个项目不是全局事件。它严格依赖于当前的视图控制器。

3 个答案:

答案 0 :(得分:13)

UIApplication has a method for just this purposeits Cocoa cousin一样。您可以使用一条消息替换问题中的所有代码。

答案 1 :(得分:12)

你非常接近。更标准的是这样的事情:

@implementation NSResponder (MyViewController)
- (void)itemSelected:(id)someItem
{
    [[self nextResponder] itemSelected:someItem];
}
@end

默认情况下,事件通常是如何将事件传递给链。然后在右侧控制器中,覆盖该方法,而不是采取自定义操作。

这可能不是您想要实现的目标的正确模式,但它是将消息传递到响应者链的好方法。

答案 2 :(得分:4)

如果你确定第一响应者设置正确,那么彼得的解决方案就有效。如果您希望更多地控制通知哪些对象的事件,则应使用targetForAction:withSender:代替。

这允许您指定应该能够处理事件的第一个视图,并从那里它将爬行响应者链,直到找到可以处理该消息的对象。

以下是您可以使用的fully documented功能:

@interface ABCResponderChainHelper
/*!
 Sends an action message identified by selector to a specified target's responder chain.
 @param action 
    A selector identifying an action method. See the remarks for information on the permitted selector forms.
 @param target 
    The object to receive the action message. If @p target cannot invoke the action, it passes the request up the responder chain.
 @param sender
    The object that is sending the action message.
 @param userInfo
    The user info for the action. This parameter may be @c nil.
 @return
    @c YES if a responder object handled the action message, @c NO if no object in the responder chain handled the message.
 @remarks
    This method pushes two parameters when calling the target. This design enables the action selector to be one of the following:
 @code
 - (void)action
 - (void)action:(id)sender
 - (void)action:(id)sender userInfo:(id)userInfo@endcode
*/
+ (BOOL)sendResponderChainAction:(SEL)action to:(UIResponder *)target from:(id)sender withUserInfo:(id)userInfo;
@end

实现:

@implementation ABCResponderChainHelper
+ (BOOL)sendResponderChainAction:(SEL)action to:(UIResponder *)target from:(id)sender withUserInfo:(id)userInfo {
    target = [target targetForAction:action withSender:sender];
    if (!target) {
        return NO;
    }

    NSMethodSignature *signature = [target methodSignatureForSelector:action];
    const NSInteger hiddenArgumentCount = 2; // self and _cmd
    NSInteger argumentCount = [signature numberOfArguments] - hiddenArgumentCount;
    switch (argumentCount) {
        case 0:
            SuppressPerformSelectorLeakWarning([target performSelector:action]);
            break;
        case 1:
            SuppressPerformSelectorLeakWarning([target performSelector:action withObject:sender]);
            break;
        case 2:
            SuppressPerformSelectorLeakWarning([target performSelector:action withObject:sender withObject:userInfo]);
            break;
        default:
            NSAssert(NO, @"Invalid number of arguments.");
            break;
    }

    return YES;
}
@end

注意:这使用SuppressPerformSelectorLeakWarning宏。

这在UITableView中很有用。想象一下,您在单元格上有一个手势识别器,您需要通知视图控制器已执行的操作。您可以使用响应程序链,而不必为单元格创建委托,然后将消息转发给委托。

样本用法(发件人):

// in table view cell class:
- (void)longPressGesture:(UILongPressGestureRecognizer *)recognizer {
    // ...
    [ABCResponderChainHelper sendResponderChainAction:@selector(longPressCell:) to:self from:self withUserInfo:nil];
}

这里self指的是细胞本身。 responder chain确保首先将该方法发送到UITableViewCell,然后发送到UITableView,最后发送到UIViewController

样本用法(接收者):

#pragma mark - Custom Table View Cell Responder Chain Messages
- (void)longPressCell:(UITableViewCell *)sender {
    // handle the long press
}

如果您尝试使用sendAction:to:from:forEvent:执行相同的操作,则会遇到两个问题:

  • 为了使其能够与响应者链一起使用,您必须将nil传递给to参数,此时它将在第一响应者处开始发送消息而不是你选择的对象。控制第一响应者可能很麻烦。使用此方法,您只需告诉它要从哪个对象开始。
  • 您无法轻松传递包含任意数据的第二个参数。您would need to subclass UIEvent并添加userInfo等属性并在事件参数中传递它;在这种情况下,这是一个笨拙的解决方案。