Reactivecocoa: Como você lida com comandos que requerem confirmação?

Criado em 10 mar. 2014  ·  9Comentários  ·  Fonte: ReactiveCocoa/ReactiveCocoa

Tenho certeza de que deve haver uma maneira mais reativa de lidar com isso, mas não consigo entender.

Eu tenho um UIButton que executa uma ação destrutiva - uma exclusão, neste caso. Meu modelo de visualização tem uma propriedade deleteCommand que retorna um comando que executa a exclusão e conectar o botão ao comando é direto.

No entanto, o que eu realmente gostaria que acontecesse é que quando o botão for pressionado, uma confirmação UIAlertView seja exibida e se o usuário clicar em OK, a exclusão será realizada ou se ele clicar em Cancelar, não será.

Este comportamento de "confirmação" parece pertencer ao modelo de visualização, pois é uma parte fundamental do comportamento da minha visualização - não quero permitir a exclusão sem confirmação primeiro.

No entanto, os detalhes de como eu obtenho essa confirmação (por exemplo, UIAlertView ) não pertencem ao modelo de visualização, pois ele não deve saber nada sobre a visualização. Parece que a apresentação real do alerta pertence ao controlador de visualização.

Então, há uma maneira agradável e elegante de fazer isso, de modo que eu ainda possa ter um único RACCommand que represente minha exclusão com a confirmação de que posso vincular meu UIButton ?

question

Todos 9 comentários

Do seu modelo de visão, exponha o sinal alerts enviando AlertViewModel s. Defina o comando apropriado para cada botão. Esta é a solução que uso em meu aplicativo.

Este comportamento de "confirmação" parece pertencer ao modelo de visualização, pois é uma parte fundamental do comportamento da minha visualização - não quero permitir a exclusão sem confirmação primeiro.

Você gostaria de modelar a "segurança sendo desativada" em seu modelo de visualização? Pode ser uma boa ideia se você tiver visualizações diferentes exibindo este tipo de modelo de visualização, mas não vincule muito fortemente à ideia de confirmação: se você exibir seu modelo em uma visualização de tabela, deslizar para a esquerda para exibir o botão de exclusão já o faria servem para remover a segurança: não há necessidade de pedir novamente a confirmação do usuário quando ele o tocar.

Eu pessoalmente escolheria uma solução mais simples: vincular o enabled do botão ao comando, mas não o comando em si, e chamar manualmente o -execute: do comando a partir de UIAlertView .

Sim, basicamente o que Denis disse. Já fiz isso várias vezes.
Em seu modelo de visualização, você tem:

  1. Um comando para sua ação inicial (exclusão, neste caso).
  2. Um sinal que envia um valor quando uma confirmação é necessária
  3. Um único comando para confirmação ou um comando para aceitar e negar

O código seria parecido com isto (não testado e sem o reforço / enfraquecimento necessário):

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

Portanto, o botão delete em seu controlador de visão começa a executar deleteCommand no modelo de visão, que imediatamente começa a ser executado. O comando delete agora está esperando que confirmationCommand.executionSignals envie um valor. A execução do comando delete dispara um valor a ser enviado em self.confirmationRequiredSignal .

Em seguida, seu controlador de visualização observa que confirmationRequiredSignal no viewModel, lança uma visualização de alerta e amarra os botões na visualização de alerta para o confirmationCommand (o comando de confirmação obteria o índice do botão por padrão, eu acredito, então você tem que mapear isso em seu confirmed ). Assim que o usuário toca em um botão, self.confirmationCommand é executado e retorna o confirmed , completando assim o ciclo. Você pode opcionalmente retornar um erro no deleteCommand como fiz aqui se você quiser talvez lançar uma mensagem de erro ou algo observando self.deleteCommand.errors . Ou você pode apenas retornar um sinal vazio para concluir a execução do comando.

Este modelo funciona com visualizações de alerta, planilhas de ação, etc. RACCommands são muito legais.

@Coneko provavelmente está certo ao dizer que uma confirmação simples não pertence necessariamente ao modelo de visualização. Mas ainda é um bom método para ter no bolso de trás para coisas como selecionar vários valores para continuar. por exemplo, alguém toca no botão log in with twitter e precisa escolher qual conta do Twitter usar (se houver várias) para continuar (ou cancelar). (Um exemplo real para o qual acabei de criar.)

@sprynmr obrigado pelo feedback,

Substituí a propriedade por um método chamado deleteWithConfirmation: que leva um único argumento - um bloco que retorna um RACSignal representando a confirmação enviando um único valor, @YES ou @NO

O comando em si é um sinal que pega o primeiro valor do sinal de confirmação e o mapeia para um sinal apropriado - o sinal de exclusão se confirmado ou um sinal vazio:

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

No controlador de visualização, eu conecto o comando ao meu botão assim:

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

Como você pode ver, o sinal que eu retorno é apenas um mapa do sinal clicado do botão para um valor SIM / NÃO.

Funciona bem o suficiente para mim! Isso ainda parece razoavelmente flexível, pois eu poderia simplesmente passar um bloco que retorna um sinal que envia um único valor SIM se eu quisesse pular a confirmação. Eu poderia até encapsular isso como um método separado em meu modelo de visualização, por exemplo:

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

Também parece uma boa solução. Vou arquivar isso.

@sprynmr Eu tentei sua resposta. No entanto, há algo que me intriga.
O sinal retornado pelo bloco nunca será concluído ( self.confirmationCommand.executionSignals.switchToLatest ). Isso significa que o comando sempre ficará no modo de execução, portanto, não pode executar nenhum outro comando.

Como você faz o código a seguir funcionar na prática?

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 adicione algo para controlar o ciclo de vida, como take:1 após switchToLatest

Também esteja atento para um ciclo de retenção lá. @ fortifique / enfraqueça suas múltiplas referências a self

Esta página foi útil?
0 / 5 - 0 avaliações