Reactivecocoa: ¿Cómo maneja los comandos que requieren confirmación?

Creado en 10 mar. 2014  ·  9Comentarios  ·  Fuente: ReactiveCocoa/ReactiveCocoa

Estoy seguro de que debe haber una forma más reactiva de lidiar con esto, pero no puedo entenderlo.

Tengo un UIButton que realiza una acción destructiva, una eliminación, en este caso. Mi modelo de vista tiene una propiedad deleteCommand que devuelve un comando que realiza la eliminación y el cableado del botón al comando es sencillo.

Sin embargo, lo que realmente me gustaría que sucediera es que cuando se presiona el botón, se muestra una confirmación UIAlertView y si el usuario presiona Aceptar, se realiza la eliminación o si presiona Cancelar, no.

Este comportamiento de "confirmación" parece que pertenece al modelo de vista, ya que es una parte fundamental del comportamiento de mi vista; no quiero permitir la eliminación sin confirmación primero.

Sin embargo, los detalles de cómo obtengo esa confirmación (por ejemplo, un UIAlertView ) no pertenecen al modelo de vista, ya que no debería saber nada sobre la vista. Parece que la presentación real de la alerta pertenece al controlador de vista.

Entonces, ¿hay una manera agradable y elegante de hacer esto, de modo que todavía pueda tener un solo RACCommand que represente mi eliminación con la confirmación de que puedo vincularme a mi UIButton ?

question

Todos 9 comentarios

Desde su modelo de vista, exponga la señal alerts que envía AlertViewModel s. Configure el comando apropiado para cada botón. Esta es la solución que utilizo en mi aplicación.

Este comportamiento de "confirmación" parece que pertenece al modelo de vista, ya que es una parte fundamental del comportamiento de mi vista; no quiero permitir la eliminación sin confirmación primero.

¿Le gustaría modelar la "seguridad desactivada" en su modelo de vista? Podría ser una buena idea si tiene diferentes vistas que muestran este tipo de modelo de vista, pero no lo vincule demasiado con la idea de confirmación: si muestra su modelo en una vista de tabla, deslizar el dedo hacia la izquierda para mostrar el botón eliminar ya lo haría. Sirve para eliminar la seguridad: no es necesario volver a pedir confirmación al usuario cuando lo toca.

Personalmente, optaría por una solución más simple: vincular el enabled del botón al comando, pero no al comando en sí, y llamar manualmente al comando -execute: desde el UIAlertView .

Sí, básicamente lo que dijo Denis. He hecho esto varias veces.
En su modelo de vista tiene:

  1. Un comando para su acción inicial (borrar en este caso).
  2. Una señal que envía un valor cuando se requiere una confirmación
  3. Un solo comando para confirmación, o un comando para aceptar y rechazar

El código se vería así (no probado y sin el reforzamiento / debilitamiento necesario):

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

Entonces, su botón de eliminar en su controlador de vista comienza a ejecutar deleteCommand en el modelo de vista, que comienza a ejecutarse inmediatamente. El comando de eliminación ahora está esperando que confirmationCommand.executionSignals envíe un valor. La ejecución del comando de eliminación activa un valor que se enviará en self.confirmationRequiredSignal .

Luego, su controlador de vista observa que confirmationRequiredSignal en el modelo de vista, arroja una vista de alerta y vincula los botones en la vista de alerta al comando de confirmación (el comando de confirmación obtendría el índice del botón de forma predeterminada, creo, por lo que lo haría tiene que mapear eso en su confirmed ). Una vez que el usuario presiona un botón, self.confirmationCommand se ejecuta y devuelve el confirmed , completando así el ciclo. Opcionalmente, puede devolver un error en deleteCommand como lo he hecho aquí si quisiera lanzar un mensaje de error o algo al observar self.deleteCommand.errors . O simplemente puede devolver una señal vacía para completar la ejecución del comando.

Este modelo funciona con vistas de alerta, hojas de acción, etc. RACCommands son realmente geniales.

@Coneko probablemente tenga razón en que una simple confirmación no necesariamente pertenece al modelo de vista. Pero sigue siendo un buen método para tenerlo en el bolsillo trasero para cosas como seleccionar entre varios valores para continuar. Por ejemplo, alguien toca el botón log in with twitter y debe elegir qué cuenta de Twitter usar (si tiene varias) para continuar (o cancelar). (Un ejemplo real para el que acabo de construir).

@sprynmr gracias por los comentarios,

Reemplacé la propiedad con un método llamado deleteWithConfirmation: que toma un solo argumento: un bloque que devuelve un RACSignal representa la confirmación enviando un valor único, @YES , o @NO

El comando en sí es una señal que toma el primer valor de la señal de confirmación y lo asigna a una señal apropiada - la señal de borrado si se confirma otra señal vacía:

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

En el controlador de vista, conecto el comando a mi botón así:

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 puede ver, la señal que devuelvo es solo un mapa desde la señal del botón pulsado hasta un valor SÍ / NO.

¡Funciona bastante bien para mí! Esto todavía parece razonablemente flexible, ya que simplemente podría pasar un bloque que devuelve una señal que envía un solo valor SÍ si quisiera omitir la confirmación. Incluso podría encapsular esto como un método separado en mi modelo de vista, por ejemplo:

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

También parece una solución bastante buena. Lo archivaré.

@sprynmr Probé tu respuesta. Sin embargo, hay algo que me desconcierta.
La señal devuelta por el bloque nunca se completará ( self.confirmationCommand.executionSignals.switchToLatest ). Significa que el comando siempre permanecerá en modo de ejecución, por lo que no puede ejecutar ningún otro comando.

¿Cómo se hace que el siguiente código funcione en la práctica?

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 agrega algo para controlar el ciclo de vida, como take:1 después de switchToLatest

También tenga cuidado con un ciclo de retención allí. @ strongify / debilitar sus múltiples referencias a self

¿Fue útil esta página
0 / 5 - 0 calificaciones