Reactivecocoa: 確認が必要なコマンドをどのように処理しますか?

作成日 2014年03月10日  ·  9コメント  ·  ソース: ReactiveCocoa/ReactiveCocoa

これに対処するためのより反応的な方法があるはずですが、私はそれを回避することができません。

破壊的なアクション(この場合は削除)を実行するUIButtonがあります。 私のビューモデルには、削除を実行するコマンドを返すdeleteCommandプロパティがあり、ボタンをコマンドに接続するのは簡単です。

しかし、私が本当にやりたいのは、ボタンをタップすると確認UIAlertViewが表示され、ユーザーが[OK]を押すと削除が実行されるか、[キャンセル]を押すと実行されないことです。

この「確認」動作は、ビューの動作の基本的な部分であるため、ビューモデルに属しているように感じます。最初に確認せずに削除を許可したくありません。

ただし、その確認を取得する方法の詳細( UIAlertView )は、ビューについて何も知らないはずなので、ビューモデルには属していません。 アラートの実際の表示はViewControllerに属しているようです。

それで、これを行うための優れたエレガントな方法がありますか?それでも、 UIButtonバインドできることを確認して、削除を表す単一のRACCommandを使用できますか?

question

全てのコメント9件

ビューモデルから、 AlertViewModel送信するalerts信号を公開します。 ボタンごとに適切なコマンドを設定します。 これは、アプリで使用するソリューションです。

この「確認」動作は、ビューの動作の基本的な部分であるため、ビューモデルに属しているように感じます。最初に確認せずに削除を許可したくありません。

ビューモデルで「安全がオフになっている」ことをモデル化したいですか? この種のビューモデルを表示するさまざまなビューがある場合は良い考えかもしれませんが、確認のアイデアとあまり緊密にバインドしないでください。テーブルビューでモデルを表示する場合、左にスワイプして削除ボタンを表示します。安全を取り除くのに役立ちます:ユーザーがそれをタップしたときに再度確認を求める必要はありません。

私は個人的にはもっと簡単な解決策を選びます。ボタンのenabledをコマンドにバインドしますが、コマンド自体にはバインドせず、 UIAlertViewからコマンドの-execute:を手動で呼び出します。

はい、基本的にデニスが言ったことです。 私はこれを複数回行いました。
ビューモデルには次のものがあります。

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

したがって、View Controllerの削除ボタンが開始すると、ViewモデルでdeleteCommandが実行され、すぐに実行が開始されます。 削除コマンドは、 confirmationCommand.executionSignalsが値を送信するのを待機しています。 deleteコマンドを実行すると、値がself.confirmationRequiredSignal送信されます。

次に、View Controllerは、viewModelのconfirmationRequiredSignalを監視し、アラートビューをスローして、アラートビューのボタンをconfirmationCommandに関連付けます(確認コマンドは、デフォルトでボタンのインデックスを取得すると思います。それをconfirmed値にマッピングする必要があります)。 ユーザーがボタンをタップすると、 self.confirmationCommandが実行され、 confirmed値が返され、サイクルが完了します。 self.deleteCommand.errors監視してエラーメッセージなどをスローしたい場合は、ここで行ったように、オプションでdeleteCommandエラーを返すことができます。 または、空の信号を返してコマンドの実行を完了することもできます。

このモデルは、アラートビュー、アクションシートなどで機能します。 RACCommandsは本当にすごいです。

@Conekoは、単純な確認が必ずしもビューモデルに属しているとは限らないということはおそらく正しいでしょう。 ただし、複数の値から選択して続行するなどの場合は、バックポケットに入れておくことをお勧めします。 たとえば、誰かがlog in with twitterボタンをタップし、続行(またはキャンセル)するには、使用するTwitterアカウント(複数ある場合)を選択する必要があります。 (私が作成したばかりの実際の例です。)

@sprynmrフィードバックに感謝します。私は同様の、しかしIMOの少し単純なソリューションを使用しました。

プロパティをdeleteWithConfirmation:というメソッドに置き換えました。このメソッドは、単一の引数、つまり単一の値、 @YES 、または@NO送信することで確認を表すRACSignalを返すブロックです。 @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 / NO値へのマップにすぎません。

私には十分に機能します! 確認をスキップしたい場合は、単一の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 take:1ように、ライフサイクルを制御するための何かを追加します

また、そこでの保持サイクルにも注意してください。 selfへの複数の参照を@ strongify / weakify

このページは役に立ちましたか?
0 / 5 - 0 評価

関連する問題

BrettThePark picture BrettThePark  ·  4コメント

danishin picture danishin  ·  4コメント

eimantas picture eimantas  ·  6コメント

v-silin picture v-silin  ·  4コメント

LunaCodeGirl picture LunaCodeGirl  ·  3コメント