Я уверен, что должен быть более реактивный способ справиться с этим, но я не могу понять это.
У меня есть UIButton, который выполняет деструктивное действие - в данном случае удаление. Моя модель представления имеет свойство deleteCommand
которое возвращает команду, выполняющую удаление, и связать кнопку с этой командой просто.
Однако мне бы очень хотелось, чтобы при нажатии кнопки отображалось подтверждение UIAlertView
и если пользователь нажимает ОК, удаление выполняется, а если он нажимает Отмена, этого не происходит.
Такое поведение «подтверждения» кажется принадлежащим модели представления, поскольку это фундаментальная часть поведения моего представления - я не хочу сначала разрешать удаление без подтверждения.
Однако особенности того, как я получаю это подтверждение (например, UIAlertView
), не относятся к модели представления, поскольку она не должна ничего знать о представлении. Похоже, что фактическое представление предупреждения принадлежит контроллеру представления.
Итак, есть ли красивый и элегантный способ сделать это, чтобы у меня все еще могла быть одна-единственная команда RACCommand, которая представляет мое удаление с подтверждением, что я могу привязать к моему UIButton
?
Из вашей модели представления выставьте alerts
signal, отправив AlertViewModel
s. Установите соответствующую команду для каждой кнопки. Это решение, которое я использую в своем приложении.
Такое поведение «подтверждения» кажется принадлежащим модели представления, поскольку это фундаментальная часть поведения моего представления - я не хочу сначала разрешать удаление без подтверждения.
Вы хотите смоделировать «отключение безопасности» в вашей модели просмотра? Это может быть хорошей идеей, если у вас есть разные представления, отображающие этот тип модели представления, но не связывайте ее слишком сильно с идеей подтверждения: если вы отображаете свою модель в виде таблицы, проведите пальцем влево, чтобы отобразить кнопку удаления, уже служат для снятия предохранителя: нет необходимости снова спрашивать пользователя о подтверждении, когда он нажимает на него.
Лично я бы выбрал более простое решение: привязать enabled
кнопки к команде, но не к самой команде, и вручную вызвать -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
, таким образом завершая цикл. Вы можете при желании вернуть ошибку в deleteCommand
как я сделал здесь, если вы хотите, возможно, выдать сообщение об ошибке или что-то еще, наблюдая за self.deleteCommand.errors
. Или вы можете просто вернуть пустой сигнал, чтобы завершить выполнение команды.
Эта модель работает с представлениями предупреждений, листами действий и т. Д. RACCommands
действительно чертовски круты.
@Coneko , вероятно, прав в том, что простое подтверждение не обязательно относится к модели представления. Но это по-прежнему хороший способ иметь в заднем кармане такие вещи, как выбор из нескольких значений для продолжения. Например, кто-то нажимает кнопку log in with twitter
, и ему нужно выбрать, какую учетную запись 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, если я хочу пропустить подтверждение. Я мог бы даже инкапсулировать это как отдельный метод в моей модели представления, например:
- (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
Также следите за циклом удержания там. @ strongify / weakify ваши множественные ссылки на self