我确信必须有一种更被动的方式来处理这个问题,但我无法理解它。
我有一个执行破坏性操作的 UIButton - 在这种情况下是删除。 我的视图模型有一个deleteCommand
属性,它返回一个执行删除和将按钮连接到命令的命令很简单。
但是,我真正希望发生的是当点击按钮时,会显示确认UIAlertView
,如果用户点击 OK,则执行删除,或者如果他们点击取消,则不会。
这种“确认”行为感觉像是属于视图模型,因为它是我的视图行为的基本部分——我不想在没有确认的情况下允许删除。
但是,我如何获得确认(例如UIAlertView
)的细节不属于视图模型,因为它不应该知道关于视图的任何信息。 似乎警报的实际呈现属于视图控制器。
那么有没有一种很好的、优雅的方式来做到这一点,这样我仍然可以有一个 RACCommand 来表示我的删除并确认我可以绑定到我的UIButton
?
从您的视图模型公开alerts
信号发送AlertViewModel
s。 为每个按钮设置适当的命令。 这是我在我的应用程序中使用的解决方案。
这种“确认”行为感觉像是属于视图模型,因为它是我的视图行为的基本部分——我不想在没有确认的情况下允许删除。
您想对视图模型中的“安全关闭”进行建模吗? 如果您有不同的视图显示这种视图模型,这可能是一个好主意,但不要将其与确认的想法绑定得太紧:如果您在表视图中显示您的模型,向左滑动以显示删除按钮作为解除安全:用户点击时无需再次询问确认。
我个人只会采用更简单的解决方案:将按钮的enabled
绑定到命令,而不是命令本身,然后从UIAlertView
手动调用命令的-execute:
UIAlertView
。
是的,基本上就是丹尼斯所说的。 我已经多次这样做了。
在您的视图模型上,您有:
代码看起来像这样(未经测试,并且没有必要的加强/削弱):
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;
}];
因此,您的视图控制器中的删除按钮开始在视图模型上执行deleteCommand
,该视图模型立即开始执行。 删除命令现在正在等待confirmationCommand.executionSignals
发送一个值。 删除命令的执行触发要在self.confirmationRequiredSignal
上发送的值。
然后你的视图控制器观察到confirmationRequiredSignal
在 viewModel 上,抛出一个警报视图并将警报视图上的按钮绑定到确认命令(我相信确认命令默认会获取按钮的索引,所以你会必须将其映射到您的confirmed
值中)。 一旦用户点击按钮, self.confirmationCommand
就会被执行,并返回confirmed
值,从而完成循环。 如果您想通过观察self.deleteCommand.errors
抛出错误消息或其他内容,您可以选择在deleteCommand
返回错误,就像我在这里所做的那样。 或者你可以只返回一个空信号来完成命令的执行。
该模型适用于警报视图、操作表等。 RACCommands
真的很酷。
@Coneko可能是对的,简单的确认不一定属于视图模型。 但这仍然是一个很好的方法,可以放在你的后兜里,以便从多个值中进行选择以继续。 例如,有人点击log in with twitter
按钮,他们需要选择要使用的推特帐户(如果他们有多个)以继续(或取消)。 (我刚刚为之构建的一个真实示例。)
@sprynmr感谢您的反馈,我采用了类似但 IMO 稍微简单的解决方案。
我用一个名为deleteWithConfirmation:
的方法替换了该属性,该方法接受一个参数 - 一个返回RACSignal
的块,通过发送单个值@YES
或@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添加一些东西来控制生命周期,比如switchToLatest
之后的take:1
switchToLatest
还要注意那里的保留周期。 @strongify/弱化您对self
多次引用