Reactivecocoa: Wie gehen Sie mit Befehlen um, die eine BestÀtigung erfordern?

Erstellt am 10. MĂ€rz 2014  Â·  9Kommentare  Â·  Quelle: ReactiveCocoa/ReactiveCocoa

Ich bin sicher, dass es eine reaktivere Art geben muss, damit umzugehen, aber ich komme nicht damit klar.

Ich habe einen UIButton, der eine destruktive Aktion ausfĂŒhrt - in diesem Fall eine Löschung. Mein Ansichtsmodell hat eine deleteCommand Eigenschaft, die einen Befehl zurĂŒckgibt, der das Löschen durchfĂŒhrt, und die VerknĂŒpfung der SchaltflĂ€che mit dem Befehl ist unkompliziert.

Was ich jedoch wirklich gerne tun wĂŒrde, ist, wenn die SchaltflĂ€che angetippt wird, eine BestĂ€tigung UIAlertView angezeigt wird und wenn der Benutzer auf OK klickt, wird das Löschen durchgefĂŒhrt oder wenn er auf Abbrechen klickt, ist dies nicht der Fall.

Dieses "BestĂ€tigungsverhalten" fĂŒhlt sich an, als ob es in das Ansichtsmodell gehört, da es ein grundlegender Teil des Verhaltens meiner Ansicht ist - ich möchte das Löschen nicht ohne vorherige BestĂ€tigung zulassen.

Die Einzelheiten, wie ich diese BestĂ€tigung erhalte (zB ein UIAlertView ) gehören jedoch nicht in das Ansichtsmodell, da es nichts ĂŒber die Ansicht wissen sollte. Es scheint, als ob die eigentliche Darstellung der Warnung in den Ansichtscontroller gehört.

Gibt es also eine nette, elegante Möglichkeit, dies zu tun, so dass ich immer noch einen einzigen RACCommand haben kann, der meine Löschung mit der BestÀtigung darstellt, dass ich an mein UIButton binden kann?

question

Alle 9 Kommentare

Machen Sie aus Ihrem Ansichtsmodell ein alerts Signal verfĂŒgbar, das AlertViewModel s sendet. Legen Sie fĂŒr jede SchaltflĂ€che den entsprechenden Befehl fest. Dies ist die Lösung, die ich in meiner App verwende.

Dieses "BestĂ€tigungsverhalten" fĂŒhlt sich an, als ob es in das Ansichtsmodell gehört, da es ein grundlegender Teil des Verhaltens meiner Ansicht ist - ich möchte das Löschen nicht ohne vorherige BestĂ€tigung zulassen.

Sie möchten das "Sicherheits-Aus" in Ihrem Ansichtsmodell modellieren? Es könnte eine gute Idee sein, wenn Sie verschiedene Ansichten haben, die diese Art von Ansichtsmodell anzeigen, aber binden Sie es nicht zu eng mit der Idee der BestÀtigung: Wenn Sie Ihr Modell in einer Tabellenansicht anzeigen, wischen Sie nach links, um die SchaltflÀche zum Löschen anzuzeigen dienen zum Entfernen der Sicherung: Der Benutzer muss nicht erneut um BestÀtigung gebeten werden, wenn er darauf tippt.

Ich persönlich wĂŒrde einfach eine einfachere Lösung wĂ€hlen: Binden Sie die enabled der SchaltflĂ€che an den Befehl, aber nicht den Befehl selbst, und rufen Sie die -execute: des Befehls manuell von UIAlertView .

Ja, im Grunde, was Denis gesagt hat. Ich habe dies mehrmals getan.
In Ihrem Ansichtsmodell haben Sie:

  1. Ein Befehl fĂŒr Ihre erste Aktion (in diesem Fall Löschen).
  2. Ein Signal, das einen Wert sendet, wenn eine BestÀtigung erforderlich ist
  3. Ein einzelner Befehl zur BestÀtigung oder ein Befehl zum Annehmen und Ablehnen

Code wĂŒrde ungefĂ€hr so ​​aussehen (ungetestet und ohne das notwendige StĂ€rken/SchwĂ€chen):

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

Ihre LöschschaltflĂ€che in Ihrem Ansichtscontroller startet also deleteCommand im Ansichtsmodell, das sofort mit der AusfĂŒhrung beginnt. Der Löschbefehl wartet nun darauf, dass confirmationCommand.executionSignals einen Wert mitsendet. Die AusfĂŒhrung des Löschbefehls löst das Senden eines Werts an self.confirmationRequiredSignal .

Dann beobachtet Ihr Ansichtscontroller, dass confirmationRequiredSignal auf dem viewModel eine Warnungsansicht auslöst und die SchaltflĂ€chen in der Warnungsansicht mit dem ConfirmationCommand verknĂŒpft (der BestĂ€tigungsbefehl wĂŒrde den Index der SchaltflĂ€che standardmĂ€ĂŸig erhalten, glaube ich, also wĂŒrden Sie mĂŒssen Sie das Ihrem confirmed Wert zuordnen). Sobald der Benutzer auf eine SchaltflĂ€che tippt, wird self.confirmationCommand ausgefĂŒhrt und gibt den confirmed Wert zurĂŒck, wodurch der Zyklus abgeschlossen wird. Sie können optional einen Fehler in deleteCommand wie ich es hier getan habe, wenn Sie vielleicht eine Fehlermeldung oder etwas ausgeben möchten, indem Sie self.deleteCommand.errors . Oder Sie könnten einfach ein leeres Signal zurĂŒckgeben, um die AusfĂŒhrung des Befehls abzuschließen.

Dieses Modell funktioniert mit Warnungsansichten, AktionsblÀttern usw. RACCommands sind wirklich verdammt cool.

@Coneko hat wahrscheinlich recht, dass eine einfache BestĂ€tigung nicht unbedingt in das Ansichtsmodell gehört. Aber es ist immer noch eine gute Methode, um Dinge in der GesĂ€ĂŸtasche zu haben, um beispielsweise aus mehreren Werten auszuwĂ€hlen, um fortzufahren. zB tippt jemand auf die SchaltflĂ€che log in with twitter und muss auswĂ€hlen, welches Twitter-Konto verwendet werden soll (falls er mehrere hat), um fortzufahren (oder abzubrechen). (Ein echtes Beispiel, fĂŒr das ich gerade gebaut habe.)

@sprynmr danke fĂŒr das Feedback, ich habe eine Ă€hnliche, aber IMO etwas einfachere Lösung gewĂ€hlt.

Ich habe die Eigenschaft durch eine Methode namens deleteWithConfirmation: die ein einzelnes Argument benötigt - ein Block, der ein RACSignal zurĂŒckgibt, das die BestĂ€tigung darstellt, indem ein einzelner Wert, @YES oder @NO

Der Befehl selbst ist ein Signal, das den ersten Wert aus dem BestÀtigungssignal nimmt und auf ein entsprechendes Signal abbildet - das Löschsignal, wenn bestÀtigt, sonst ein leeres Signal:

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

Im Ansichtscontroller verdrahte ich den Befehl wie folgt mit meiner SchaltflÀche:

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

Wie Sie sehen können, ist das Signal, das ich zurĂŒcksende, nur eine Zuordnung von dem auf die SchaltflĂ€che geklickten Signal zu einem JA/NEIN-Wert.

Funktioniert bei mir gut genug! Dies scheint immer noch einigermaßen flexibel zu sein, da ich einfach einen Block ĂŒbergeben könnte, der ein Signal zurĂŒckgibt, das einen einzelnen JA-Wert sendet, wenn ich die BestĂ€tigung ĂŒberspringen möchte. Ich könnte dies sogar als separate Methode in meinem Ansichtsmodell kapseln, zum Beispiel:

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

Scheint auch eine ziemlich gute Lösung zu sein. Ich werde das ablegen.

@sprynmr Ich habe deine Antwort ausprobiert. Allerdings verwundert mich etwas.
Das vom Block zurĂŒckgegebene Signal wird nie abgeschlossen ( self.confirmationCommand.executionSignals.switchToLatest ). Dies bedeutet, dass der Befehl immer im AusfĂŒhrungsmodus bleibt, sodass keine anderen Befehle ausgefĂŒhrt werden können.

Wie funktioniert der folgende Code in der Praxis?

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 fĂŒge etwas hinzu, um den Lebenszyklus zu kontrollieren, wie ein take:1 nach switchToLatest

Achten Sie auch dort auf einen Retain-Zyklus. @strongify/weakify Ihre mehrfachen Verweise auf self

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

mdiep picture mdiep  Â·  5Kommentare

iby picture iby  Â·  5Kommentare

RuiAAPeres picture RuiAAPeres  Â·  3Kommentare

toddbluhm picture toddbluhm  Â·  5Kommentare

dougmcbride picture dougmcbride  Â·  3Kommentare