Аркуш виявлення було відхилено в iOS 13


120

До iOS 13 представлені контролери перегляду використовували для покриття всього екрану. І, коли було відхилено, viewDidAppearбуло виконано функцію батьківського контролера подання .

Тепер iOS 13 буде представляти контролери перегляду у вигляді аркуша за замовчуванням, а це означає, що картка частково покриває базовий контролер подання, а це означає, що viewDidAppearне буде викликатися, оскільки батьківський контролер подання насправді ніколи не зникав.

Чи є спосіб виявити, що представлений аркуш контролера подання був відхилений ? Якусь іншу функцію я можу замінити в батьківському контролері подання, а не використовувати якийсь делегат ?


5
Добре обговорено в developer.apple.com/videos/play/wwdc2019/224
— matt

Тож чи є спосіб відхилити всі модальні аркуші одночасно до кореневого vc?
— Веслі,


Чому вам потрібно знати, коли його було звільнено? Якщо потрібно перезавантажити дані та оновити інтерфейс, сповіщення або KVO можуть бути гарною альтернативою.
— martn_st

Відповіді:


61

Чи можна виявити, що представлений аркуш контролера подання був відхилений?

Так.

Якусь іншу функцію я можу замінити в батьківському контролері подання, а не використовувати якийсь делегат?

Ні. "Якийсь делегат" - це як ви це робите. Зробіть себе делегатом контролера презентацій і перевизначте його presentationControllerDidDismiss(_:).

https://developer.apple.com/documentation/uikit/uiadaptivepresentationcontrollerdelegate/3229889-presentationcontrollerdiddismiss


Відсутність загальної події, згенерованої під час виконання, яка інформує вас про те, що представлений контролер подання, незалежно від того, повноекранний, був відхилений, справді є проблемою; але це не нова проблема, оскільки завжди існували не повноекранні контролери подання. Просто зараз (в iOS 13) їх більше! Я присвячую окремі питання-відповіді на цю тему в іншому місці: уніфікований UIViewController "став передовим" виявленням? .


6
цього недостатньо. Якщо у вашому представленому ВК є наббер і кнопка користувальницької панелі, яка програмно відхиляє ваш вигляд, контролер презентацій відхилення не викликається.
— Ірина

15
Привіт, @Irina! Якщо ви програмуєте своє подання програмно, вам не потрібен зворотний дзвінок, оскільки ви відхилили свій погляд програмно - ви знаєте, що зробили це тому, що зробили це. Метод делегування є лише у випадку, якщо це робить користувач .
— matt

6
@matt Дякую за відповідь. Коли програмовий вигляд відхиляється, це не викликається (як каже Ірина), і ви маєте рацію, що ми знаємо, що ми це зробили. Я просто думаю, що є зайва кількість шаблонного коду, який слід написати, щоб отримати своєрідний 'viewWillAppear' з новим модальним стилем презентації в iOS13. Це стає особливо брудним, коли ви керуєте маршрутизацією через архітектуру, в якій маршрутизація витягується (у координаторах MVVM + або типу маршрутизатора у VIPER)
— Адам Уейт,

3
@AdamWaite Я згоден, але ця проблема не нова. Ми маємо цю проблему роками, з поповерами, з не повноекранними представленими контролерами перегляду, з попередженнями тощо. Я розглядаю це як серйозну ваду в репертуарі Apple "подій". Я просто кажу, яка реальність і чому. Я безпосередньо
— matt

1
ПрезентаціяControllerDidDismiss (_ :). не викликається, коли я натискаю кнопку назад у Child VC. Будь-яка допомога?
— Крішна Міна,

42

Ось приклад коду батьківського контролера перегляду, який отримує повідомлення, коли дочірній контролер перегляду, який він представляє у вигляді аркуша (тобто, за замовчуванням iOS 13), відхиляється:

public final class Parent: UIViewController, UIAdaptivePresentationControllerDelegate
{
  // This is assuming that the segue is a storyboard segue; 
  // if you're manually presenting, just see the delegate there.
  public override func prepare(for segue: UIStoryboardSegue, sender: Any?)
  {
    if segue.identifier == "mySegue" {
      segue.destination.presentationController?.delegate = self;
    }
  }

  public func presentationControllerDidDismiss(
    _ presentationController: UIPresentationController)
  {
    // Only called when the sheet is dismissed by DRAGGING.
    // You'll need something extra if you call .dismiss() on the child.
    // (I found that overriding dismiss in the child and calling
    // presentationController.delegate?.presentationControllerDidDismiss
    // works well).
  }
}

Відповідь Jerland2 заплутана, оскільки (а) оригінальний запитувач хотів отримати виклик функції при звільненні аркуша (тоді як він реалізував презентаціюControllerDidAttemptToDismiss, яка викликається, коли користувач намагається і не може відхилити аркуш), і (b) параметр isModalInPresentation є повністю ортогональною і насправді зробить представлений аркуш неприпустимим (що протилежне бажанню OP).


6
Це добре працює. Тільки підказка, що якщо ви використовуєте навігаційний контролер у своєму VC, що називається, вам слід призначити контролер навігації як презентаціюController?, Делегувати (не VC, яку навігаційна система має як topViewController).
— instAustralia

@instAustralia, чи можете ви пояснити, чому, або посилатися на документацію? Дякую.
— Ахмед Усама

ПрезентаціяControllerDidDismiss Як його викликати, коли користувач натискає кнопку назад?
— Krishna Meena,

@AhmedOsama - контролер навігації є контролером презентацій і тому є делегатом, оскільки саме він відповідатиме на звільнення. Я спробував і VC, який вбудований у Nav Controller, але саме тут існують мої фактичні кнопки для звільнення та відповіді. Я не можу знайти його безпосередньо в документах Apple, але на нього посилається тут sarunw.com/posts/modality-changes-in-ios13
— instAustralia

26

Ще один варіант повернутися viewWillAppearі viewDidAppearвстановлений

let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen

ця опція охоплює повноекранний режим і після звільнення викликає наведені вище методи


2
Дякую PiterPan. Це працює. Це чудове і найшвидше вирішення.
— Erkam KUCET

Дякуємо за цей швидкий та надійний спосіб відновити колишню поведінку за замовчуванням. Чудово мати можливість миттєво встановити це виправлення, а потім раціонально спланувати перехід до нової поведінки.
— Ian Lovejoy

14
Це обхідний шлях, а не виправлення. Для всіх не найкраще просто повернутися до таблиць стилів iOS 12. IOS 13 - це круто! :)
— Метт,

1
будьте обережні, використовуючи це для iPad, оскільки iPad за замовчуванням відображається як аркуш сторінок, якщо він представлений модально. Це змусить iPad представитись у повноекранному режимі
— wyu

не працює для мене. Я відкриваю модальний контролер. закрийте це звільненням, але воля, здається, не називається. Чому? подяка
— neo999

20

Для майбутніх читачів ось більш повна відповідь із реалізацією:

  1. У кореневому режимі подання контролерів для підготовки до segue додайте наступне (припускаючи, що ваш модаль має навігаційний контролер)
    // Modal Dismiss iOS 13
    modalNavController.presentationController?.delegate = modalVc
  1. У контролері модального перегляду додайте такий метод делегат +
// MARK: - iOS 13 Modal (Swipe to Dismiss)

extension ModalViewController: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {


        print("slide to dismiss stopped")
        self.dismiss(animated: true, completion: nil)
    }
}
  1. Переконайтеся в модальному контролері перегляду, що вказана нижче властивість є істинною, щоб викликати метод делегата
    self.isModalInPresentation = true
  1. Прибуток

1
self.isModalInPresentation = true, тоді перетягування не працює. видалити, що метод делегування рядка все ще називається добре. Дякую.
— Йогеш Патель,

2
Це заплутано, оскільки (а) оригінальний запитувач хотів отримати виклик функції, коли аркуш відхилено (тоді як ви реалізували презентаціюControllerDidAttemptToDismiss, яка викликається, коли користувач намагається та не зможе відхилити аркуш), і (b) параметр isModalInPresentation є повністю ортогональною і насправді зробить представлений аркуш неприпустимим (що протилежне бажанню OP).
— Matt

1
Слідкуйте за пунктом відповіді @Matt (a): Використання presentationControllerDidDismissмає спрацювати
— гондо

Не зовсім коректно, оскільки presentationControllerDidAttemptToDismissпризначений для випадків, коли користувач намагався відмовитись, але йому було попереджено програмно (уважно прочитайте документ щодо цього методу). presentationControllerWillDismissМетод є один для виявлення наміру користувача розпускати АБО presentationControllerShouldDismissконтролювати відхиляючи АБО presentationControllerDidDismissдля виявлення факту звільнення
— Віталія

5

Стрімкий

Загальне рішення для дзвінка viewWillAppear в iOS13

class ViewController: UIViewController {

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            print("viewWillAppear")
        }

        //Show new viewController
        @IBAction func show(_ sender: Any) {
            let newViewController = NewViewController()
            //set delegate of UIAdaptivePresentationControllerDelegate to self
            newViewController.presentationController?.delegate = self
            present(newViewController, animated: true, completion: nil)
        }
    }

    extension UIViewController: UIAdaptivePresentationControllerDelegate {
        public func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
            if #available(iOS 13, *) {
                //Call viewWillAppear only in iOS 13
                viewWillAppear(true)
            }
        }
    }

1
Це обробляє лише звільнення за допомогою слайда зверху, а не за допомогою виклику функції dismiss(_).
— Педро Паулу Аморім

3

Функція DRAG OR CALL DISMISS FUNC працюватиме з кодом нижче.

1) У кореневому контролері подання ви вказуєте те, що є його контролером подання подання, як показано нижче

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "presenterID" {
        let navigationController = segue.destination as! UINavigationController
        if #available(iOS 13.0, *) {
            let controller = navigationController.topViewController as! presentationviewcontroller
            // Modal Dismiss iOS 13
            controller.presentationController?.delegate = self
        } else {
            // Fallback on earlier versions
        }
        navigationController.presentationController?.delegate = self

    }
}

2) Знову ж у кореневому контролері подання ви повідомляєте, що робитимете, коли його контролер подання подання буде відхилено

public func presentationControllerDidDismiss(
  _ presentationController: UIPresentationController)
{
    print("presentationControllerDidDismiss")
}

1) У контролері подання презентації, коли ви натискаєте кнопку скасування або збереження на цьому зображенні. Нижче буде викликаний код

self.dismiss(animated: true) {
        self.presentationController?.delegate?.presentationControllerDidDismiss?(self.presentationController!)
    }

введіть тут опис зображення


1
чи потрібно перекидати NavigationController.topViewController на презентаціюViewController? Я вважаю, що це не так
— Фіцю

Як я можу перезавантажити дані у батьківському ВК після відхилення від дочірнього ВК кнопки "Скасувати"?
— Krishna Meena,

3

Замінити viewWillDisappear на UIViewController, який відхилено. Це сповістить вас про звільнення за допомогою isBeingDismissedлогічного прапора.

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if isBeingDismissed {
        print("user is dismissing the vc")
    }
}

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


Що щодоself.dismiss(animated: Bool, completion: (() -> Void)?)
— iGhost

0

Якщо ви хочете щось зробити, коли користувач закриває модальний аркуш із цього аркуша. Припустимо, у вас вже є якась кнопка Закрити з @IBActionлогікою та логікою, щоб показати попередження перед закриттям або зробити щось інше. Ви просто хочете виявити момент, коли користувач змушує натискати на такий контролер.

Ось як:

class MyModalSheetViewController: UIViewController {

     override func viewDidLoad() {
        super.viewDidLoad()

        self.presentationController?.delegate = self
     }

     @IBAction func closeAction(_ sender: Any) {
         // your logic to decide to close or not, when to close, etc.
     }

}

extension MyModalSheetViewController: UIAdaptivePresentationControllerDelegate {

    func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
        return false // <-prevents the modal sheet from being closed
    }

    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        closeAction(self) // <- called after the modal sheet was prevented from being closed and leads to your own logic
    }
}

-1

Якщо хтось не має доступу до представленого контролера перегляду, він може просто замінити наведений нижче спосіб у поданні контролера перегляду і змінити modalPresentationStyleна fullScreenабо може додати одну із стратегій, згаданих вище, за допомогою цього підходу

 override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    if let _ = viewControllerToPresent as? TargetVC {
        viewControllerToPresent.modalPresentationStyle = .fullScreen
    }
    super.present(viewControllerToPresent, animated: flag, completion: completion)
}

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

if let _ = (viewControllerToPresent as? UINavigationController)?.viewControllers.first as? TargetVC {
   viewControllerToPresent.modalPresentationStyle = .fullScreen
}

-2

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

ConsultarController controllerConsultar = this.Storyboard.InstantiateViewController ("ConsultarController") як ConsultarController; controllerConsultar.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; this.NavigationController.PushViewController (controllerConsultar, істина);


Повторює існуючі відповіді.
— matt

-3

З моєї точки зору, Apple не повинна встановлювати pageSheetза замовчуваннямmodalPresentationStyle

Я хотів би повернути fullScreenстиль за замовчуванням за допомогоюswizzling

Подобається це:

private func _swizzling(forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
    if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
       let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension UIViewController {

    static func preventPageSheetPresentationStyle () {
        UIViewController.preventPageSheetPresentation
    }

    static let preventPageSheetPresentation: Void = {
        if #available(iOS 13, *) {
            _swizzling(forClass: UIViewController.self,
                       originalSelector: #selector(present(_: animated: completion:)),
                       swizzledSelector: #selector(_swizzledPresent(_: animated: completion:)))
        }
    }()

    @available(iOS 13.0, *)
    private func _swizzledPresent(_ viewControllerToPresent: UIViewController,
                                        animated flag: Bool,
                                        completion: (() -> Void)? = nil) {
        if viewControllerToPresent.modalPresentationStyle == .pageSheet
                   || viewControllerToPresent.modalPresentationStyle == .automatic {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        _swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
    }
}

А потім поставте цей рядок до вашого AppDelegate

UIViewController.preventPageSheetPresentationStyle()

1
Це геніально, але я не можу з цим погодитися. Це хакі, і, більш конкретно, це суперечить зернисті iOS 13. Ви повинні використовувати "карткові" презентації в iOS 13. Відповідь, яку Apple очікує від нас, це не "обхід"; це "перебор".
— matt

Погодьтеся з цим, це рішення не допомагає використовувати стиль презентації картки як те, що нас заохочує Apple. Однак, встановивши його як стиль за замовчуванням, існуючі рядки коду десь presentingViewControllerviewWillAppear
— jacob

1
Так, але, як я вже говорив у своїй власній відповіді, це завжди було проблемою для повноекранних презентацій (таких як покази та аркуш сторінок / форм на iPad), тож це не є новим. Просто зараз цього більше. Покладатися на це viewWillAppearбуло в певному сенсі завжди неправильно. Звичайно, мені не подобається, коли Apple приходить і вирізує підлогу з-під мене. Але, як я вже сказав, нам просто потрібно жити з цим і робити все по-новому.
— matt

У моєму проекті є кілька сценаріїв, про які я не знаю, де представлений контролер подання (викликаний presentedController), і не знаю, що саме таке presentingViewController. Наприклад: у деяких випадках мені доводиться використовувати, UIViewController.topMostViewController()що повертає мені найпопулярніший контролер перегляду у поточному вікні. Отже, чому б я хотів зробити swizzling, щоб зберегти поточну поведінку, щоб робити правильні речі (оновлення даних, інтерфейс користувача) у viewWillAppearмоїх контролерах перегляду. Якщо у вас є ідеї щодо їх вирішення, будь ласка, допоможіть.
— jacob

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

-6

чи не просто було б викликати presentingViewController.viewWillAppear? перед звільненням?

self.presentingViewController?.viewWillAppear(false)
self.dismiss(animated: true, completion: nil)

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