Подтвердить что ты не робот

Прогулка по цепочке ответчиков для передачи пользовательских событий. Это неправильно?

Согласно документации 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, если на то пошло)?

ИЗМЕНИТЬ

Что я хочу достичь?

У меня есть контроллер представления, у которого есть представление, у которого есть subviews и т.д.... Несколько подзонов имеют определенный тип, который показывает элемент из базы данных. Когда пользователь отбирает это представление, на контроллер должен быть отправлен сигнал, чтобы перейти к странице подробностей этого элемента.

Вид, управляющий краном, находится на нескольких уровнях ниже основного вида в иерархии представлений. Я должен сказать контроллеру (или, в некоторых случаях, конкретному подзону "вверх по цепочке" ), что элемент был выбран.

Прослушивание уведомлений было бы вариантом, но мне это не нравится, потому что выбор элемента не является глобальным событием. Он строго привязан к текущему контроллеру.

4b9b3361

Ответ 1

Ты довольно близко. Что будет более стандартным, это примерно так:

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

Как правило, события по умолчанию передаются по цепочке. Затем в правильном контроллере переопределите этот метод, чтобы вместо этого выполнить произвольное действие.

Это может быть неправильный шаблон для того, чего вы хотите достичь, но это хороший способ передать сообщения в цепочку ответчиков.

Ответ 3

Решение Peter работает, если вы уверены, что первый ответчик настроен правильно. Если вы хотите получить больше контроля над тем, какой объект уведомлен о событиях, вы должны использовать targetForAction:withSender:.

Это позволяет вам указать первое представление, которое должно иметь возможность обрабатывать событие, и оттуда оно сканирует цепочку ответчиков, пока не найдет объект, который может обрабатывать сообщение.

Вот полностью документированная функция, которую вы можете использовать:

@interface ABCResponderChainHelper
/*!
 Sends an action message identified by selector to a specified target 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)[email protected]
*/
+ (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, после чего он начнет обмен сообщениями с первым ответчиком, а не с объектом по вашему выбору. Управление первым ответчиком может быть громоздким. С помощью этого метода вы просто скажете, с какого объекта начать.
  • Вы не можете легко передать второй аргумент с произвольными данными. Вам нужен подкласс UIEvent и добавить свойство, например userInfo, и передать это в аргументе события; неуклюжее решение в этом случае.