Das Erkennungsblatt wurde unter iOS 13 verworfen

105

Vor iOS 13 wurden View Controller vorgestellt, mit denen der gesamte Bildschirm abgedeckt wurde. Beim Entlassen wurde die übergeordnete Ansichtssteuerungsfunktion viewDidAppearausgeführt.

Jetzt zeigt iOS 13 View Controller standardmäßig als Blatt an, was bedeutet, dass die Karte den zugrunde liegenden View Controller teilweise abdeckt, was bedeutet, dass viewDidAppeardieser nicht aufgerufen wird, da der übergeordnete View Controller nie wirklich verschwunden ist.

Gibt es eine Möglichkeit zu erkennen, dass das präsentierte View Controller Sheet verworfen wurde ? Eine andere Funktion, die ich im übergeordneten Ansichts-Controller überschreiben kann, anstatt eine Art Delegat zu verwenden ?

Marcos Tanaka
quelle
Gibt es also eine Möglichkeit, alle Modalblätter gleichzeitig an den Root-VC zu senden?
Weslie
Warum müssen Sie wissen, wann es entlassen wurde? Wenn Sie Daten neu laden und die Benutzeroberfläche aktualisieren möchten, sind Benachrichtigungen oder KVO möglicherweise eine gute Alternative.
martn_st

Antworten:

53

Gibt es eine Möglichkeit zu erkennen, dass das präsentierte View Controller Sheet verworfen wurde?

Ja.

Eine andere Funktion, die ich im übergeordneten Ansichtscontroller überschreiben kann, anstatt eine Art Delegat zu verwenden?

Nein. "Eine Art Delegierter" ist, wie Sie es tun. Machen Sie sich zum Delegierten des Präsentationscontrollers und überschreiben Sie ihnpresentationControllerDidDismiss(_:) .

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


Das Fehlen eines allgemeinen zur Laufzeit generierten Ereignisses, das Sie darüber informiert, dass ein dargestellter Ansichtscontroller, ob im Vollbildmodus oder nicht, entlassen wurde, ist in der Tat problematisch. Dies ist jedoch kein neues Problem, da es immer Ansichts-Controller gab, die nicht im Vollbildmodus dargestellt wurden. Es ist nur so, dass es jetzt (in iOS 13) mehr davon gibt! Ich widme diesem Thema an anderer Stelle eine separate Frage und Antwort: Unified UIViewController wurde zur "vordersten" Erkennung? .

matt
quelle
6
das ist nicht genug. Wenn Sie einen Nabber in Ihrer präsentierten VC und eine benutzerdefinierte Leistenschaltfläche haben, die Ihre Ansicht programmgesteuert schließt, wird der Präsentations-Controller, der entlassen wurde, nicht aufgerufen.
Irina
13
Hallo @Irina - wenn Sie Ihre Ansicht programmgesteuert schließen, benötigen Sie keinen Rückruf, weil Sie Ihre Ansicht programmgesteuert geschlossen haben - Sie wissen, dass Sie es getan haben, weil Sie es getan haben. Die Delegate-Methode ist nur für den Fall, dass der Benutzer dies tut.
Matt
5
@matt Danke für die Antwort. Wenn die Ansicht programmgesteuert verworfen wird, wird sie nicht aufgerufen (wie Irina sagt), und Sie haben Recht, dass wir wissen, dass wir es getan haben. Ich denke nur, dass es unnötig viel Boilerplate-Code gibt, um eine Art 'viewWillAppear' mit dem neuen modalen Präsentationsstil in iOS13 zu erhalten. Besonders chaotisch wird es, wenn Sie das Routing über eine Architektur verwalten, in der das Routing extrahiert wird (z. B. in MVVM + -Koordinatoren oder in VIPER als Routertyp)
Adam Waite,
3
@AdamWaite Ich stimme zu, aber dieses Problem ist nicht neu. Wir haben dieses Problem seit Jahren, mit Popovers, mit nicht im Vollbildmodus dargestellten View Controllern, mit Warnungen und so weiter. Ich betrachte dies als einen schwerwiegenden Fehler in Apples Repertoire an "Ereignissen". Ich sage nur, was die Realität ist und warum. Ich setze
matt
1
PresentationControllerDidDismiss (_ :). Wird nicht aufgerufen, wenn ich in Child VC auf die Schaltfläche "Zurück" klicke. Hilft das?
Krishna Meena
39

Hier ist ein Codebeispiel für einen übergeordneten Ansichts-Controller, der benachrichtigt wird, wenn der untergeordnete Ansichts-Controller, den er als Blatt darstellt (dh in der Standardmethode für iOS 13), verworfen wird:

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 Antwort ist verwirrt, da (a) die ursprünglichen Fragesteller einen Funktionsaufruf erhalten wollten , wenn das Blatt wird entlassen (während er presentationControllerDidAttemptToDismiss implementiert, das , wenn der Benutzer versucht , aufgerufen wird und nicht um das Blatt zu entlassen) und (b) Einstellung isModalInPresentation ist völlig orthogonal und macht das vorgestellte Blatt in der Tat unentbehrlich (was das Gegenteil von dem ist, was OP will).

Matt
quelle
6
Das funktioniert gut. Nur ein Tipp: Wenn Sie einen Navigationscontroller auf Ihrem angerufenen VC verwenden, sollten Sie den Navigationscontroller als Präsentationscontroller? Delegieren (nicht den VC, den das Navigationsgerät als topViewController hat).
instAustralia
@instAustralia Können Sie erklären, warum oder auf eine Dokumentation verweisen? Vielen Dank.
Ahmed Osama
PresentationControllerDidDismiss Wie wird es aufgerufen, wenn der Benutzer die Zurück-Taste drückt?
Krishna Meena
@AhmedOsama - Der Navigationscontroller ist der Präsentationscontroller und daher der Delegierte, da er auf die Entlassung reagiert. Ich habe den VC ausprobiert, der auch in den Nav Controller eingebettet ist, aber hier sind meine eigentlichen Schaltflächen zum Schließen vorhanden und reagieren. Ich kann es nicht direkt in Apple-Dokumenten finden, aber es wird hier referenziert. Sarunw.com/posts/modality-changes-in-ios13
instAustralia
27

Eine weitere Option, um zurück zu kommen viewWillAppearund viewDidAppearist eingestellt

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

Diese Option deckt den Vollbildmodus ab und ruft nach dem Schließen die oben genannten Methoden auf

PiterPan
quelle
2
Vielen Dank, dass Sie PiterPan. Das funktioniert. Das ist großartig und am schnellsten zu lösen.
Erkam KUCET
Vielen Dank für diesen schnellen und zuverlässigen Weg, um das frühere Standardverhalten wiederherzustellen. Es ist großartig, diesen Fix sofort einsetzen zu können und dann auf rationale Weise einen Übergang zum neuen Verhalten zu planen.
Ian Lovejoy
10
Dies ist eher eine Problemumgehung als eine Lösung. Es ist nicht jedermanns Sache, einfach auf iOS 12-Stylesheets zurückzugreifen. Die iOS 13 sind cool! :)
Matt
1
Seien Sie vorsichtig, wenn Sie dies für das iPad verwenden, da das iPad bei modaler Darstellung standardmäßig als pageSheet dargestellt wird. Dies wird das iPad zwingen, sich als FullScreen zu präsentieren
wyu
nicht für mich arbeiten. Ich öffne den Modal Controller. schließe es mit entlassen, aber das willAppear wird nicht aufgerufen. Warum? danke
neo999
19

Für zukünftige Leser ist hier eine vollständigere Antwort mit der Implementierung:

  1. Fügen Sie in den Root-View-Controllern, die sich auf den Übergang vorbereiten, Folgendes hinzu (vorausgesetzt, Ihr Modal verfügt über einen Navi-Controller).
    // Modal Dismiss iOS 13
    modalNavController.presentationController?.delegate = modalVc
  1. Fügen Sie im Modal View Controller die folgende Delegate + -Methode hinzu
// 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. Stellen Sie im modalen View Controller sicher, dass die folgende Eigenschaft wahr ist, damit die Delegatmethode aufgerufen wird
    self.isModalInPresentation = true
  1. Profitieren
Jerland2
quelle
1
self.isModalInPresentation = true, dann funktioniert Drag-Dism nicht. Das Entfernen dieser Zeilendelegatmethode wird weiterhin als OK bezeichnet. Danke.
Yogesh Patel
1
Dies ist verwirrend, da (a) der ursprüngliche Fragesteller einen Funktionsaufruf erhalten wollte, wenn das Blatt geschlossen wird (während Sie PresentationControllerDidAttemptToDismiss implementiert haben, das aufgerufen wird, wenn der Benutzer versucht, das Blatt zu schließen, und (b) isModalInPresentation einstellt ist völlig orthogonal und macht das vorgestellte Blatt in der Tat unentbehrlich (was das Gegenteil von dem ist, was OP will).
Matt
Follow-up für @Matts Antwortpunkt (a): Verwenden presentationControllerDidDismisssollte funktionieren
Gondo
4

Schnell

Allgemeine Lösung zum Aufrufen viewWillAppear von 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)
            }
        }
    }
Dimo Hamdy
quelle
Dies behandelt Entlassungen nur mit der Folie von oben, nicht durch Aufrufen der Funktion dismiss(_).
Pedro Paulo Amorim
3

DRAG OR CALL DISMISS FUNC funktioniert mit dem folgenden Code.

1) Im Root-View-Controller teilen Sie das, was der Präsentations-View-Controller ist, als folgenden Code mit

 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) Wiederum im Root View Controller teilen Sie mit, was Sie tun werden, wenn der Präsentations View Controller deaktiviert ist

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

1) Wenn Sie im Controller der Präsentationsansicht in diesem Bild auf die Schaltfläche Abbrechen oder Speichern klicken. Der folgende Code wird aufgerufen

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

Geben Sie hier die Bildbeschreibung ein

Codierer
quelle
1
Ist es notwendig, navigationController.topViewController in PresentationViewController umzuwandeln? Ich finde es nicht
Fitsyu
Wie kann ich Daten in der übergeordneten VC neu laden, nachdem ich die untergeordnete VC aus der Schaltfläche Abbrechen entfernt habe?
Krishna Meena
3

Überschreiben Sie viewWillDisappear auf dem UIViewController, der entlassen wird. Sie werden über das isBeingDismissedBoolesche Flag auf eine Entlassung aufmerksam gemacht .

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

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

** Wenn sich der Benutzer in der Mitte des Wischens befindet und die Karte wieder nach oben wischt, wird sie weiterhin als entlassen registriert, auch wenn die Karte nicht entlassen wird. Aber das ist ein Randfall, den Sie vielleicht nicht interessieren.

Kunst
quelle
Was ist mitself.dismiss(animated: Bool, completion: (() -> Void)?)
iGhost
0

Wenn jemand keinen Zugriff auf die präsentierte View - Controller, können sie außer Kraft setzen nur die folgende Methode bei der Präsentation View - Controller und die ändern modalPresentationStylezu fullScreenkönnen oder eine der Strategien mit diesem Ansatz oben genannten hinzufügen

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

Wenn der dargestellte Ansichts-Controller ein Navigations-Controller ist und Sie den Root-Controller überprüfen möchten, können Sie die obige Bedingung wie folgt ändern

if let _ = (viewControllerToPresent as? UINavigationController)?.viewControllers.first as? TargetVC {
   viewControllerToPresent.modalPresentationStyle = .fullScreen
}
Kamran Khan
quelle
-2

Wenn Sie den ModalPresentationStyle in FullScreen verwendet haben, ist das Verhalten des Controllers wieder wie gewohnt.

ConsultarController controllerConsultar = this.Storyboard.InstantiateViewController ("ConsultarController") als ConsultarController; controllerConsultar.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; this.NavigationController.PushViewController (controllerConsultar, true);

Abelardo del Engel Quiroz
quelle
Wiederholt vorhandene Antworten.
Matt
-3

Aus meiner Sicht sollte Apple nicht pageSheetdie Standardeinstellung festlegenmodalPresentationStyle

Ich möchte den fullScreenStil mithilfe von wieder auf den Standard zurücksetzenswizzling

So was:

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

Und dann setzen Sie diese Zeile zu Ihrem AppDelegate

UIViewController.preventPageSheetPresentationStyle()
Jacob
quelle
1
Das ist genial, aber ich kann dem nicht zustimmen. Es ist hackig und geht, genauer gesagt, gegen iOS 13. Sie sollten in iOS 13 "Karten" -Präsentationen verwenden. Die Antwort, die Apple von uns erwartet, lautet nicht " Umgehen ". es ist "darüber hinwegkommen".
Matt
Stimmen Sie Ihrem Standpunkt zu, diese Lösung hilft nicht, den Kartenpräsentationsstil so zu verwenden, wie Apple uns ermutigt. Wenn Sie es jedoch als Standardstil festlegen, werden die vorhandenen Codezeilen irgendwo fehlerhaft, da presentingViewControllersie nicht ausgelöst werdenviewWillAppear
Jacob
1
Ja, aber wie ich bereits in meiner eigenen Antwort gesagt habe, war dies bei Nicht-Vollbild-Präsentationen (wie Popovers und Seiten- / Formularblättern auf dem iPad) immer ein Problem. Das ist also nichts Neues. Es ist nur so, dass es jetzt mehr davon gibt. Sich darauf zu verlassen viewWillAppearwar in gewissem Sinne immer falsch. Natürlich mag ich es nicht, wenn Apple mitkommt und den Boden unter mir herausschneidet. Aber wie gesagt, wir müssen einfach damit leben und die Dinge neu machen.
Matt
In meinem Projekt gibt es einige Szenarien, in denen ich nicht weiß, wo ein View Controller (aufgerufen presentedController) präsentiert wird, und auch nicht weiß, was genau das ist presentingViewController. Zum Beispiel: In einigen Fällen muss ich verwenden, UIViewController.topMostViewController()was mir den obersten Ansichts-Controller im aktuellen Fenster zurückgibt. Deshalb möchte ich das Swizzling machen, um das aktuelle Verhalten beizubehalten und die richtigen Dinge (Daten aktualisieren, Benutzeroberfläche) in viewWillAppearmeinen View Controllern zu tun . Wenn Sie Ideen zur Lösung haben, helfen Sie bitte.
Jacob
Nun, die Lösung, auf die ich am Ende meiner Antwort verweise, funktioniert, um das zu lösen, glaube ich. Die Konfiguration zur Präsentationszeit erfordert einige Arbeit, garantiert jedoch im Grunde, dass jeder Präsentator (einschließlich eines Präsentators von Warnungen) hört, wenn der präsentierte Ansichts-Controller entlassen wird.
Matt
-5

Wäre es nicht einfach, den PresentingViewController.viewWillAppear aufzurufen? vor der Entlassung?

self.presentingViewController?.viewWillAppear(false)
self.dismiss(animated: true, completion: nil)
Mikesch8764
quelle
4
Sie können nicht anrufen.
Matt