Аркуш виявлення було відхилено в 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.