Виявити, коли представлений контролер подання відхилено


82

Скажімо, у мене є екземпляр класу контролера перегляду, який називається 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 не дзвонили.


2
у VC1 буде викликаний метод viewWillAppear
— Іштван

1
Згідно з документами, контролер, що представляє, відповідає за фактичне звільнення. Коли представлений контролер відмовляється від себе, він попросить ведучого зробити це за нього. Отже, якщо ви перевизначите dismissViewControllerAnimatedсвій контролер VC1, я вважаю, що він буде викликаний, коли ви натиснете "Скасувати" на VC2. Виявіть звільнення, а потім зателефонуйте версії супер класів, яка здійснить фактичне звільнення.
— Rory McKinnel

1
Ви можете перевірити свою заміну, зателефонувавши [self.presentingViewController dismissViewControllerAnimated]. Можливо, внутрішній код має інший механізм прохання ведучого зробити відмову.
— Rory McKinnel

@RoryMcKinnel: Використання self.presentingViewController справді працювало в моїй лабораторії VC2, а також із справжньої чорної скриньки. Якщо ви дасте свої коментарі у відповіді, тоді я виберу це як відповідь. Дякую.
— user523234

Рішення для цього можна знайти у цій відповідній публікації: stackoverflow.com/a/34571641/3643020
— Campbell_Souped

Відповіді:


64

Згідно з документами, справжній звільнення несе відповідальний контролер. Коли представлений контролер відмовляється від себе, він попросить ведучого зробити це за нього. Отже, якщо ви перевизначите dismissViewControllerAnimated у своєму контролері VC1, я вважаю, що він буде викликаний, коли ви натиснете кнопку скасувати на VC2. Виявіть звільнення, а потім зателефонуйте версії супер класів, яка здійснить фактичне звільнення.

Як було встановлено з обговорення, це, здається, не працює. Замість того , щоб покладатися на базовий механізм, замість виклику dismissViewControllerAnimated:completionна самому VC2, телефонуйте dismissViewControllerAnimated:completionза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
Просто хоче подякувати та оцінити, як гарно це працює .. навіть через 4 роки! Дякую!
— Анна

48

Існує спеціальне Логічне властивість всередині UIViewControllerназивається , isBeingDismissedщо ви можете використовувати для цієї мети:

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

3
Найпростіша найкраща відповідь, правильно вирішує більшість проблем і не потребує додаткових реалізацій.
— повторна команда

Він не працює належним чином, не поєднуючись із viewDidAppear.
— Дмитро

9
У модальній презентації iOS13 це буде вірно, коли користувач почне перетягувати контролер для звільнення, але він може вирішити не завершувати звільнення.
— Естель

44

Використовуйте властивість блоку

Заявіть у VC2

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

Налаштування у VC1

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

Зателефонуйте у VC2, коли збираєтеся звільнити

onDoneBlock!(true)

@ Bryce64 Це не працює для мене, я отримав "Потік 1: Фатальна помилка: Несподівано знайдено нуль під час розгортання необов’язкового значення", в точці, де код переходить до onDoneBlock! (Правда)
— Лукас,

@Lucas Здається, ти не правильно оголосив це у VC1. "!" змушує розгортання змусити помилку, якщо її неправильно налаштовано.
— brycejl

1
Припускає, що представлений лише один контролер перегляду. Ви могли б бути в навігаційному стеку, бог знає де.
— Lee Probert

@LeeProbert Точно. У нас є представлений контролер навігації з приблизно 10 можливими дочірніми контролерами в його стеці, і майже кожен з них може спровокувати звільнення ... у цій ситуації будь-який блок завершення повинен бути переданий всім 10 таким контролерам
— Ігор Васильєв

13

Викликати може як контролер подання, так і представленийdismissViewController:animated: , щоб закрити представлений контролер перегляду.

Перший варіант є (безперечно) "правильним", з точки зору дизайну: Той самий "батьківський" контролер перегляду відповідає як за представлення, так і за відмову від модального ("дочірнього") контролера перегляду.

Однак останнє є більш зручним: як правило, кнопка "відхилити" приєднується до подання представленого контролера перегляду, і він зазначив, що контролер перегляду встановлений як його ціль дії.

Якщо ви застосовуєте попередній підхід, ви вже знаєте рядок коду у вашому поточному контролері подання, де відбувається звільнення: або запустіть свій код одразу після dismissViewControllerAnimated:completion: , або в блоці завершення.

Якщо ви застосовуєте останній підхід (представлений контролер подання відмовляється від себе), майте на увазі, що виклик dismissViewControllerAnimated:completion:із представленого контролера перегляду змушує UIKit по черзі викликати цей метод на представленому контролері подання:

Обговорення

Контролер подання представлення відповідає за відмову від представленого ним контролера перегляду. Якщо ви викликаєте цей метод на самому представленому контролері подання, UIKit просить представлений контролер подання обробити відмову.

( джерело: UIViewController Reference Reference )

Отже, щоб перехопити таку подію, ви можете замінити цей метод у контролері подання представлення:

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

    // Your custom code here...
}

1
Нема проблем. Але виявляється, це працює не так, як очікувалося. На щастя, відповідь @ RoryMcKinnel, схоже, дає більше можливостей.
— Ніколас Міарі

Хоча цей підхід є достатньо загальним для підкласування контролера перегляду з базового оголошення контролера перегляду, яке замінює dismissViewControllerAnimated в цьому. Але це не вдається. Якщо ви спробуєте загорнути контролер подання в контролер перегляду навігації
— hariszaman

4
Це не викликається, коли користувач відхиляє контролер модального перегляду, проводячи пальцем зверху!
— Дмитро

3
extension Foo: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        //call whatever you want
    }
}

vc.presentationController?.delegate = foo

1
iOS 13.0+тільки
— гондо

2

Ви можете використовувати розмотувати segue, щоб виконати це завдання, не потрібно використовувати dismissModalViewController. Визначте метод розмотування у вашому VC1.

Дивіться це посилання про те, як створити розмотувальну сегу, https://stackoverflow.com/a/15839298/5647055 .

Якщо припустити, що ваш розмотувальний фільм налаштовано, у методі дії, визначеному для вашої кнопки "Скасувати", ви можете виконати відтворення як -

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

Тепер, коли ви натискаєте кнопку "Скасувати" у VC2, вона буде відхилена і з'явиться VC1. Він також викликатиме метод розмотування, визначений у VC1. Тепер ви знаєте, коли представлений контролер подання буде відхилено.


2

Я використовую наступне, щоб сигналізувати координатору, що контролер перегляду "виконаний". Це використовується в AVPlayerViewControllerпідкласі в програмі tvOS і буде викликано після завершення переходу відхилення playerVC:

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?()
        }
    })
  }
}

Ви не повинні успадковувати від AVPLayerViewController. Документи Apple говорять: "Підклас AVPlayerViewController і перевизначення його методів не підтримуються, що призводить до невизначеної поведінки."
— Неру

2

Використання 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
    }
}

1

@ user523234 - "Але функція dismissViewControllerAnimated у VC1 не викликалася."

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

  • sourceViewController
  • presentingViewController
  • presentViewController

У вашому прикладі, VC1 = sourceViewController, VC2 = presentedViewController,?? = presentingViewController - може бути , VC1 може і ні.

Тим не менш, ви завжди можете покластися на виклик VC1.animationControllerForDismissedController (якщо ви реалізували методи делегування) під час відміни VC2, і в цьому методі ви можете робити те, що хочете, з VC1


1

Я бачив цю публікацію стільки разів, коли мав справу з цією проблемою, думав, що, нарешті, можу пролити світло на можливу відповідь.

Якщо вам потрібно знати, чи ініціювані користувачем дії (наприклад, жести на екрані) призводили до відмови для UIActionController , і ви не хочете витрачати час на створення підкласів чи розширень чи чогось іншого у своєму коді, є альтернатива.

Як з'ясовується, popoverPresentationController властивість UIActionController (або, скоріше, будь UIViewController до того ефекту), має делегат можна встановити в будь-який час у вашому коді, який має тип UIPopoverPresentationControllerDelegate , і має наступні методи:

Призначте делегата з вашого контролера дій, застосуйте вибрані вами методи в класі делегатів (view, view controller або інший) і voila!

Сподіваюся, це допомагає.


І це застаріло з iOS 13. Doh
— pronebird

1

Іншим варіантом є прослуховування dismissalTransitionDidEnd () вашого користувацького UIPresentationController


0
  1. Створіть один файл класу (.h / .m) і назвіть його: DismissSegue
  2. Виберіть підклас: UIStoryboardSegue

  3. Перейдіть до файлу DismissSegue.m і запишіть такий код:

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  4. Відкрийте раскадровку, а потім Ctrl + перетягніть з кнопки скасування на VC1 і виберіть Action Segue як Dismiss, і все готово.


0

Якщо ви перевизначаєте контролер подання, який зменшується:

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

Принаймні це спрацювало у мене.


@JohnScalo не відповідає дійсності, чимало “рідних” ієрархій контролерів подання реалізують себе за допомогою дочірніх / батьківських примітивів.
— mxcl


0

overrideІнг viewDidAppearзробив трюк для мене. Я використовував синглтон у своєму модальному режимі, і тепер я можу встановити та отримати з нього всередині дзвінка, що викликає, модальний та деінде.


viewDidAppear?
— Дмитро

1
Ви мали на увазі viewDidDisappear?
— Дейл

0

viewWillDisappearФункція заміни у представленому контролері подання.

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

Він не працює належним чином, не поєднуючись із viewDidAppear.
— Дмитро

1
Обов’язково зателефонуйте на super.viewWillDisappear
— Дейл

0

Як уже зазначалося, рішення полягає у використанні 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) } }


0

Якщо ви хочете впоратись із закриттям контролера перегляду, скористайтеся наведеним нижче кодом.

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

На жаль, ми не можемо викликати завершення в перевизначеному методі - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;оскільки цей метод викликається лише в тому випадку, якщо ви викликаєте метод звільнення цього контролера подання.


Але viewWillDisappearтакож не працює коректно, не поєднуючись із viewDidAppear.
— Дмитро

viewWillDisappear викликається, коли ВК повністю покритий (Наприклад, модальний). Можливо, вас не звільнили
— Лу Франко

0

Я використовував deinit для ViewController

deinit {
    dataSource.stopUpdates()
}

Деинициализатор викликається безпосередньо перед вивільненням екземпляра класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.