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


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.