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
?
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:
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