Reactivecocoa: Как вы обрабатываете команды, требующие подтверждения?

Созданный на 10 мар. 2014  ·  9Комментарии  ·  Источник: ReactiveCocoa/ReactiveCocoa

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

У меня есть UIButton, который выполняет деструктивное действие - в данном случае удаление. Моя модель представления имеет свойство deleteCommand которое возвращает команду, выполняющую удаление, и связать кнопку с этой командой просто.

Однако мне бы очень хотелось, чтобы при нажатии кнопки отображалось подтверждение UIAlertView и если пользователь нажимает ОК, удаление выполняется, а если он нажимает Отмена, этого не происходит.

Такое поведение «подтверждения» кажется принадлежащим модели представления, поскольку это фундаментальная часть поведения моего представления - я не хочу сначала разрешать удаление без подтверждения.

Однако особенности того, как я получаю это подтверждение (например, UIAlertView ), не относятся к модели представления, поскольку она не должна ничего знать о представлении. Похоже, что фактическое представление предупреждения принадлежит контроллеру представления.

Итак, есть ли красивый и элегантный способ сделать это, чтобы у меня все еще могла быть одна-единственная команда RACCommand, которая представляет мое удаление с подтверждением, что я могу привязать к моему UIButton ?

question

Все 9 Комментарий

Из вашей модели представления выставьте alerts signal, отправив AlertViewModel s. Установите соответствующую команду для каждой кнопки. Это решение, которое я использую в своем приложении.

Такое поведение «подтверждения» кажется принадлежащим модели представления, поскольку это фундаментальная часть поведения моего представления - я не хочу сначала разрешать удаление без подтверждения.

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

Лично я бы выбрал более простое решение: привязать enabled кнопки к команде, но не к самой команде, и вручную вызвать -execute: из UIAlertView .

Да в основном то, что сказал Денис. Я делал это несколько раз.
В вашей модели просмотра у вас есть:

  1. Команда для вашего начального действия (в данном случае удаления).
  2. Сигнал, который отправляет значение, когда требуется подтверждение.
  3. Одна команда для подтверждения или команда для принятия и отклонения

Код будет выглядеть примерно так (непроверенный и без необходимости усиление / ослабление):

self.deleteCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id objectToDelete) { 
    [return self.confirmationCommand.executionSignals
        switchToLatest]
            flattenMap:^id(NSNumber *confirmed) {
                if(confirmed) {
                    return [self deleteObject:objectToDelete];
                } else {
                    return [RACSignal error:someErrorWithAMessageMaybe];
                }
            }];
}];

self.confirmationRequiredSignal = [self.deleteCommand.executing
    filter: ^BOOL(NSNumber *executing) {
        return executing.boolValue;
    }];

Таким образом, ваша кнопка удаления в вашем контроллере представления начинает выполнять deleteCommand в модели представления, которая немедленно начинает выполняться. Команда удаления теперь ожидает, пока confirmationCommand.executionSignals отправит значение. Выполнение команды удаления запускает отправку значения на self.confirmationRequiredSignal .

Затем ваш контроллер представления наблюдает, что confirmationRequiredSignal на viewModel, вызывает представление предупреждения и связывает кнопки в представлении предупреждения с командой подтверждения (команда подтверждения получит индекс кнопки по умолчанию, я полагаю, поэтому вы должны необходимо сопоставить это с вашим значением confirmed ). Как только пользователь нажимает кнопку, выполняется self.confirmationCommand и возвращается значение confirmed , таким образом завершая цикл. Вы можете при желании вернуть ошибку в deleteCommand как я сделал здесь, если вы хотите, возможно, выдать сообщение об ошибке или что-то еще, наблюдая за self.deleteCommand.errors . Или вы можете просто вернуть пустой сигнал, чтобы завершить выполнение команды.

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

@Coneko , вероятно, прав в том, что простое подтверждение не обязательно относится к модели представления. Но это по-прежнему хороший способ иметь в заднем кармане такие вещи, как выбор из нескольких значений для продолжения. Например, кто-то нажимает кнопку log in with twitter , и ему нужно выбрать, какую учетную запись Twitter использовать (если их несколько), чтобы продолжить (или отменить). (Реальный пример, для которого я только что построил.)

@sprynmr спасибо за отзыв, я пошел с аналогичным, но немного более простым решением IMO.

Я заменил свойство методом deleteWithConfirmation: который принимает единственный аргумент - блок, который возвращает RACSignal представляющее подтверждение, отправив одно значение, @YES или @NO

Сама команда представляет собой сигнал, который берет первое значение из сигнала подтверждения и отображает его в соответствующий сигнал - сигнал удаления, если он подтвержден, иначе пустой сигнал:

- (RACCommand *)deleteWithConfirmation:(RACSignal*(^)(void))signalBlock
{
    NSParameterAssert(signalBlock);

    @weakify(self);

    return [[RACCommand alloc] initWithEnabled:nil signalBlock:^RACSignal *(id input) {
        @strongify(self);

        RACSignal *confirmationSignal = signalBlock();

        return [[confirmationSignal take:1] flattenMap:^RACStream *(id value) {
            if ([value boolValue]) {
                return [self doDeletion]; // for example
            }
            else {
                return [RACSignal empty];
            }
        }];
    }];
}

В контроллере представления я подключаю команду к кнопке следующим образом:

self.deleteButton.rac_command = [self.viewModel deleteWithConfirmation:^RACSignal *{
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Delete Widget" message:@"Are you sure you want to delete this widget?" delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:@"Delete", nil];

        [alert show];

        return [alert.rac_buttonClickedSignal map:^id(NSNumber *buttonIndex) {
            return @([buttonIndex integerValue] == 1);
        }];
    }];

Как видите, возвращаемый мной сигнал - это просто карта от сигнала нажатия кнопки до значения ДА / НЕТ.

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

- (RACCommand *)deleteWithoutConfirmationCommand
{
    return [self deleteWithConfirmation:^RACSignal *{
        return [RACSignal return:@YES];
    }]
}

Кажется, тоже неплохое решение. Я сохраню это.

@sprynmr Я попробовал ваш ответ. Однако есть кое-что меня озадачивающее.
Сигнал, возвращаемый блоком, никогда не будет завершен ( self.confirmationCommand.executionSignals.switchToLatest ). Это означает, что команда всегда будет оставаться в режиме выполнения, поэтому она не может выполнять никакие другие команды.

Как заставить следующий код работать на практике?

self.deleteCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id objectToDelete) { 
    [return self.confirmationCommand.executionSignals
        switchToLatest]
            flattenMap:^id(NSNumber *confirmed) {
                if(confirmed) {
                    return [self deleteObject:objectToDelete];
                } else {
                    return [RACSignal error:someErrorWithAMessageMaybe];
                }
            }];
}];

@haifengkao добавить что-нибудь для управления жизненным циклом, например take:1 после switchToLatest

Также следите за циклом удержания там. @ strongify / weakify ваши множественные ссылки на self

Была ли эта страница полезной?
0 / 5 - 0 рейтинги