Обнаруживать, когда представленный диспетчер просмотра отклонен

Скажем, у меня есть экземпляр класса контроллера вида VC2. В VC2 есть кнопка "Отмена", которая будет отклонена. Но я не могу обнаружить или получить обратный вызов, когда кнопка "отменить" получила триггер. VC2 - черный ящик.

Контроллер вида (называемый VC1) будет представлять VC2 с использованием метода presentViewController:animated:completion:.

Какие параметры VC1 должны обнаружить, когда VC2 был уволен?

Изменить: из комментария @rory mckinnel и ответа @NicolasMiari я попробовал следующее:

В VC2:

-(void)cancelButton:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:^{

    }];
//    [super dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

В VC1:

//-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
- (void)dismissViewControllerAnimated:(BOOL)flag
                           completion:(void (^ _Nullable)(void))completion
{
    NSLog(@"%s ", __PRETTY_FUNCTION__);
    [super dismissViewControllerAnimated:flag completion:completion];
//    [self dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

Но dismissViewControllerAnimated в VC1 не вызывался.

Ответы

Ответ 1

Согласно документам, диспетчер представления несет ответственность за фактическое увольнение. Когда представленный диспетчер уволит себя, он попросит ведущего сделать это за него. Поэтому, если вы переопределите функцию rejectViewControllerAnimated в контроллере VC1, я верю, что он будет вызван, когда вы нажмете cancel на VC2. Обнаружите увольнение, а затем вызовите версию суперклассов, которая будет действительно увольнять.

Как видно из обсуждения, это не работает. Вместо того, чтобы полагаться на основной механизм, вместо вызова dismissViewControllerAnimated:completion на самом VC2, вызовите dismissViewControllerAnimated:completion on self.presentingViewController в VC2. Затем это вызовет ваше переопределение напрямую.

Лучшим подходом в целом было бы обеспечить, чтобы VC2 обеспечивал блок, который вызывается, когда модальный контроллер завершен.

Итак, в VC2 укажите свойство блока с именем onDoneBlock.

В VC1 вы представляете следующее:

  • В VC1 создайте VC2

  • Установите обработчик для VC2 как: VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • Представьте контроллер VC2 как обычно, используя [self presentViewController: VC2 анимированный: ДА завершение: ноль];

  • В VC2 в вызове отмены целевого действия self.onDoneBlock();

В результате VC2 сообщает, кто бы это ни делал, что он сделан. Вы можете расширить onDoneBlock, чтобы иметь аргументы, указывающие, удалось ли модальное комлетирование, аннулирование и т.д....

Ответ 2

Использовать свойство блокировки

Объявить в VC2

var onDoneBlock : ((Bool) -> Void)?

Настройка в VC1

VC2.onDoneBlock = { result in
                // Do something
            }

Вызов в VC2, когда вы собираетесь отклонить

onDoneBlock!(true)

Ответ 3

Контроллер представляемого представления и могут представлять dismissViewController:animated:, чтобы отклонить представленный контроллер представления.

Первый вариант (возможно) является "правильным" в плане дизайна: один и тот же "родительский" контроллер представления отвечает как за представление, так и за отклонение модального ("дочернего") контроллера представления.

Однако последний вариант более удобен: обычно кнопка "отклонить" прикрепляется к представленному представлению контроллера представления, и в качестве цели его действия указан контроллер представления.

Если вы применяете прежний подход, вы уже знаете строку кода в своем представительном контроллере представления, где происходит увольнение: либо запустите ваш код сразу после dismissViewControllerAnimated:completion:, либо в пределах блока завершения.

Если вы принимаете последний подход (представленный контроллер представления отклоняет себя), имейте в виду, что вызов dismissViewControllerAnimated:completion: из представленного контроллера представления приводит к тому, что UIKit, в свою очередь, вызывает этот метод на текущем контроллере представления:

Discussion

Представляющий контроллер представления отвечает за отклоняя представленный контроллер. Если вы вызываете этот метод на представленном контроллере вида UIKit просит представить просмотр контроллера для обработки увольнения.

(источник: описание класса UIViewController)

Итак, чтобы перехватить такое событие, вы можете переопределить этот метод в представляемом контроллере представления:

override func dismiss(animated flag: Bool,
                         completion: (() -> Void)?) {
    super.dismiss(animated: flag, completion: completion)

    // Your custom code here...
}

Ответ 4

Существует специальное Логическое свойство внутри UIViewController называется isBeingDismissed, которые вы можете использовать для этой цели:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if isBeingDismissed {
        // TODO: Do your stuff here.
    }
}

Ответ 5

Вы можете использовать unind segue для выполнения этой задачи, не нужно использовать функцию rejectModalViewController. Определите метод размотки segue в вашем VC1.

См. эту ссылку о том, как создать раскручивание segue, fooobar.com/questions/14221/....

Предполагая, что ваш режим размотки настроен, в методе действий, определенном для вашей кнопки "Отмена", вы можете выполнить сеанс как -

[self performSegueWithIdentifier:@"YourUnwindSegueName" sender:nil];

Теперь, всякий раз, когда вы нажимаете кнопку "Отмена" в VC2, он будет отклонен и появится VC1. Он также будет вызывать метод разматывания, определенный в VC1. Теперь вы знаете, когда представленный контроллер просмотра отклонен.

Ответ 6

Я использую следующее, чтобы сообщить координатору, что контроллер просмотра "сделан". Это используется в подклассе AVPlayerViewController в приложении tvOS и вызывается после завершения перехода на увольнение игрока:

class PlayerViewController: AVPlayerViewController {
  var onDismissal: (() -> Void)?

  override func beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) {
    super.beginAppearanceTransition(isAppearing, animated: animated)
    transitionCoordinator?.animate(alongsideTransition: nil,
      completion: { [weak self] _ in
         if !isAppearing {
            self?.onDismissal?()
        }
    })
  }
}

Ответ 7

@user523234 - "Но отклонениеViewControllerAnimated в VC1 не вызывалось."

Вы не можете предположить, что VC1 на самом деле делает презентацию - это может быть контроллер корневого представления, VC0. В нем задействовано 3 контроллера:

  • sourceViewController
  • presentingViewController
  • presentedViewController

В вашем примере VC1 = sourceViewController, VC2 = presentedViewController, ?? = presentingViewController - возможно, VC1, возможно, нет.

Однако вы всегда можете полагаться на вызываемый VC1.animationControllerForDismissedController(если вы реализовали методы делегата) при отклонении VC2, и в этом методе вы можете делать то, что хотите, с помощью VC1

Ответ 8

Использование willMove(toParent: UIViewController?) Следующим образом показалось мне willMove(toParent: UIViewController?). (Проверено на iOS12).

override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent);

    if parent == nil
    {
        // View controller is being removed.
        // Perform onDismiss action
    }
}

Ответ 9

  • Создайте один файл класса (.h/.m) и назовите его: DismissSegue
  • Выберите подкласс: UIStoryboardSegue

  • Перейдите в файл DismissSegue.m и запишите следующий код:

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  • Откройте раскадровку, а затем Ctrl + перетащите ее с кнопки отмены на VC1 и выберите "Действие Segue as Dismiss" и вы закончите.

Ответ 11

override viewDidAppear сделал viewDidAppear дело для меня. Я использовал синглтон в моём модале и теперь могу устанавливать и получать его из вызывающего VC, модального и везде.

Ответ 12

Переопределить функцию viewWillDisappear в представленном контроллере представления.

override func viewWillDisappear(_ animated: Bool) {
    //Your code here
}

Ответ 13

Как уже упоминалось, решение состоит в том, чтобы использовать override func dismiss(animated flag: Bool, completion: (() → Void)? = nil).

Для тех, кто интересуется, почему override func dismiss(animated flag: Bool, completion: (() → Void)? = nil) не всегда работает, вы можете обнаружить, что вызов перехватывается UINavigationController если им управляют. Я написал подкласс, который должен помочь:

class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() → Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }

Ответ 14

Я видел этот пост очень много раз, когда имел дело с этой проблемой, и я подумал, что смогу наконец пролить свет на возможный ответ.

Если вам нужно знать, вызывало ли пользовательские действия (например, жесты на экране) увольнение для UIActionController, и не хотите тратить время на создание подклассов или расширений или чего-либо еще в вашем коде, есть альтернатива.

Как выясняется, popoverPresentationController свойство UIActionController (или, скорее, любой UIViewController к тому эффекту), имеет делегат можно установить в любое время в вашем коде, который имеет тип UIPopoverPresentationControllerDelegate, и имеет следующие методы:

Назначьте делегата из вашего контроллера действий, внедрите выбранные вами методы в класс делегата (view, view controller или любой другой) и вуаля!

Надеюсь это поможет.

Ответ 15

Если вы хотите обработать отклонение контроллера представления, вы должны использовать код ниже.

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (self.isBeingDismissed && self.completion != NULL) {
        self.completion();
    }
}

К сожалению, мы не можем вызвать завершение в переопределенном методе - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion; потому что этот метод вызывается, только если вы вызываете метод dismiss этого контроллера представления.

Ответ 16

Если вы переопределили на управляющем контроллере вида:

override func removeFromParentViewController() {
    super.removeFromParentViewController()
    // your code here
}

По крайней мере, это сработало для меня.