Erkennen, wann ein dargestellter Ansichts-Controller entlassen wird

81

Angenommen, ich habe eine Instanz einer View-Controller-Klasse namens VC2. In VC2 gibt es eine Schaltfläche "Abbrechen", die sich selbst schließt. Ich kann jedoch keinen Rückruf erkennen oder empfangen, wenn die Schaltfläche "Abbrechen" ausgelöst wurde. VC2 ist eine Black Box.

Ein Ansichts-Controller (VC1 genannt) präsentiert VC2 mithilfe der presentViewController:animated:completion:Methode.

Welche Optionen muss VC1 erkennen, wenn VC2 entlassen wurde?

Bearbeiten: Aus dem Kommentar von @rory mckinnel und der Antwort von @NicolasMiari habe ich Folgendes versucht:

In VC2:

-(void)cancelButton:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:^{

    }];
//    [super dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

In 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:^{
//        
//    }];
}

Aber der dismissViewControllerAnimatedim VC1 wurde nicht angerufen.

user523234
quelle
1
In VC1 wird die viewWillAppear-Methode aufgerufen
Istvan
1
Gemäß den Dokumenten ist der anwesende Controller für die tatsächliche Entlassung verantwortlich. Wenn sich der vorgestellte Controller selbst entlässt, fordert er den Präsentator auf, dies zu tun. Wenn Sie also dismissViewControllerAnimatedIhren VC1-Controller überschreiben, wird er meiner Meinung nach aufgerufen, wenn Sie auf VC2 auf Abbrechen klicken. Erkennen Sie die Entlassung und rufen Sie dann die Superklassenversion auf, die die eigentliche Entlassung ausführt.
Rory McKinnel
1
Sie können Ihre Überschreibung testen, indem Sie anrufen [self.presentingViewController dismissViewControllerAnimated]. Es kann sein, dass der innere Code einen anderen Mechanismus hat, um den Präsentator zu bitten, die Entlassung vorzunehmen.
Rory McKinnel
@RoryMcKinnel: Die Verwendung von self.presentingViewController funktionierte sowohl in meinem Labor VC2 als auch in der echten Black Box. Wenn Sie Ihre Kommentare in die Antwort einfügen, werde ich sie als Antwort auswählen. Vielen Dank.
user523234
Eine Lösung hierfür finden Sie in diesem verwandten Beitrag: stackoverflow.com/a/34571641/3643020
Campbell_Souped

Antworten:

64

Gemäß den Dokumenten ist der anwesende Controller für die tatsächliche Entlassung verantwortlich. Wenn sich der vorgestellte Controller selbst entlässt, fordert er den Präsentator auf, dies zu tun. Wenn Sie also descViewControllerAnimated in Ihrem VC1-Controller überschreiben, wird es meiner Meinung nach aufgerufen, wenn Sie auf VC2 auf Abbrechen klicken. Erkennen Sie die Entlassung und rufen Sie dann die Superklassenversion auf, die die eigentliche Entlassung ausführt.

Wie aus der Diskussion hervorgeht, scheint dies nicht zu funktionieren. Anstatt sich auf den zugrunde liegenden Mechanismus zu verlassen, anstatt dismissViewControllerAnimated:completionVC2 selbst aufzurufen, rufen Sie dismissViewControllerAnimated:completionaufself.presentingViewController in VC2. Dadurch wird Ihre Überschreibung direkt aufgerufen.

Insgesamt wäre es besser, wenn VC2 einen Block bereitstellt, der aufgerufen wird, wenn der Modal Controller fertig ist.

Geben Sie in VC2 eine Blockeigenschaft mit dem Namen an onDoneBlock .

In VC1 präsentieren Sie Folgendes:

  • Erstellen Sie in VC1 VC2

  • Stellen Sie den Fertig-Handler für VC2 wie folgt ein: VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • Präsentieren Sie den VC2-Controller wie gewohnt mit [self presentViewController: VC2 animiert: JA Vervollständigung: Null];

  • In VC2 im Aufruf "Zielaktion abbrechen" self.onDoneBlock();

Das Ergebnis ist, dass VC2 jedem, der es auslöst, mitteilt, dass es fertig ist. Sie können die onDoneBlockArgumente erweitern, um anzugeben, ob das Modal komletiert, abgebrochen, erfolgreich usw. ist.

Rory McKinnel
quelle
2
Ich möchte mich nur bedanken und schätzen, wie schön das funktioniert ... auch nach 4 Jahren! Vielen Dank!
Anna
45

Es gibt eine spezielle Boolesche Eigenschaft innerhalb UIViewControllergenannt , isBeingDismisseddass Sie für diesen Zweck verwendet werden können:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if isBeingDismissed {
        // TODO: Do your stuff here.
    }
}
Joris Weimar
quelle
3
Die einfachste beste Antwort, behebt die meisten Probleme korrekt und erfordert keine zusätzlichen Implementierungen.
Reojased
Ohne Pairing funktioniert es nicht richtig viewDidAppear.
Dmitry
9
In einer modalen iOS13-Präsentation ist dies der Fall, wenn ein Benutzer den Controller zum Entlassen zieht, er jedoch entscheiden kann, die Entlassung nicht abzuschließen.
Estel
44

Verwenden Sie eine Block-Eigenschaft

In VC2 deklarieren

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

Setup in VC1

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

Rufen Sie VC2 an, wenn Sie entlassen werden

onDoneBlock!(true)
brycejl
quelle
@ Bryce64 Es funktioniert nicht für mich, ich habe "Thread 1: Schwerwiegender Fehler: Unerwartet Null beim Auspacken eines optionalen Werts gefunden", an dem Punkt, an dem der Code an onDoneBlock geht! (True)
Lucas
@Lucas Klingt so, als hätten Sie es in VC1 nicht richtig deklariert. Das "!" Erzwingt das Auspacken, um einen Fehler zu erzwingen, wenn Sie ihn nicht richtig eingerichtet haben.
Brycejl
1
Angenommen, es wird nur ein View Controller angezeigt. Sie könnten auf einem Navigationsstapel sein, weiß Gott wo.
Lee Probert
@ LeeProbert Genau. Wir haben einen vorgestellten Navigationscontroller mit ungefähr 10 möglichen untergeordneten Controllern auf der Seite seines Stapels, und fast jeder von ihnen kann die Entlassung auslösen ... In dieser Situation müsste jeder Abschlussblock an alle 10 solcher Controller übergeben werden
Igor Vasilev
13

Sowohl der präsentierende als auch der präsentierte View Controller können aufrufendismissViewController:animated: , um den präsentierten Ansichtscontroller zu schließen.

Die erstere Option ist (wohl) die "richtige", was das Design betrifft: Derselbe "übergeordnete" Ansichts-Controller ist für das Präsentieren und Löschen des modalen ("untergeordneten") Ansichts-Controllers verantwortlich.

Letzteres ist jedoch praktischer: In der Regel ist die Schaltfläche "Entlassen" an die Ansicht des dargestellten Ansichtscontrollers angehängt, und der Ansichtscontroller wurde als Aktionsziel festgelegt.

Wenn Sie den früheren Ansatz verwenden, kennen Sie bereits die Codezeile in Ihrem Presenting View Controller, in der die Entlassung erfolgt: Führen Sie Ihren Code entweder unmittelbar danach aus dismissViewControllerAnimated:completion: oder innerhalb des Abschlussblocks aus.

Wenn Sie den letzteren Ansatz verwenden (der Controller für präsentierte Ansichten schließt sich selbst ab), denken Sie daran, dass der Aufruf dismissViewControllerAnimated:completion:vom Controller für präsentierte Ansichten UIKit veranlasst, diese Methode wiederum auf dem Controller für präsentierte Ansichten aufzurufen:

Diskussion

Der präsentierende View Controller ist dafür verantwortlich, den präsentierten View Controller zu schließen. Wenn Sie diese Methode auf dem präsentierten Ansichts-Controller selbst aufrufen, fordert UIKit den präsentierenden Ansichts-Controller auf, die Entlassung zu behandeln.

( Quelle: UIViewController-Klassenreferenz )

Um ein solches Ereignis abzufangen, können Sie diese Methode im darstellenden Ansichts-Controller überschreiben :

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

    // Your custom code here...
}
Nicolas Miari
quelle
1
Kein Problem. Aber es stellt sich heraus, dass es nicht wie erwartet funktioniert. Zum Glück scheint die Antwort von @ RoryMcKinnel mehr Optionen zu bieten.
Nicolas Miari
Obwohl dieser Ansatz generisch genug ist, um den Ansichts-Controller von einer Basis-Ansichts-Controller-Anzeige zu unterklassifizieren, überschreibt dies dies. Aber es schlägt fehl Wenn Sie versuchen, in einem View Controller in Navigation View Controller
Hariszaman
4
Es wird nicht aufgerufen, wenn der Benutzer den Modal View Controller durch Wischen von oben schließt!
Dmitry
3
extension Foo: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        //call whatever you want
    }
}

vc.presentationController?.delegate = foo
Ivan
quelle
1
iOS 13.0+nur
Gondo
2

Sie können die Abwicklungssegmentierung verwenden, um diese Aufgabe auszuführen, ohne den EntlassungModalViewController verwenden zu müssen. Definieren Sie in Ihrem VC1 eine Abwicklungsmethode.

Unter diesem Link erfahren Sie, wie Sie den Abwicklungsbereich erstellen: https://stackoverflow.com/a/15839298/5647055 .

Angenommen, Ihr Abwicklungsbereich ist eingerichtet. In der für Ihre Schaltfläche "Abbrechen" definierten Aktionsmethode können Sie den Übergang wie folgt ausführen:

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

Wenn Sie jetzt die Taste "Abbrechen" im VC2 drücken, wird sie geschlossen und VC1 wird angezeigt. Es wird auch die in VC1 definierte Abwicklungsmethode aufgerufen. Jetzt wissen Sie, wann der dargestellte Ansichts-Controller geschlossen wird.

valarMorghulis
quelle
2

Ich benutze das Folgende, um einem Koordinator zu signalisieren, dass der View Controller "fertig" ist. Dies wird in einer AVPlayerViewControllerUnterklasse in einer tvOS-Anwendung verwendet und wird aufgerufen, nachdem der Übergang zur Entlassung von playerVC abgeschlossen ist:

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?()
        }
    })
  }
}
Obstcodierer
quelle
Sie sollten nicht von AVPLayerViewController erben. In Apple-Dokumenten heißt es: "Das Unterklassen von AVPlayerViewController und das Überschreiben seiner Methoden wird nicht unterstützt und führt zu undefiniertem Verhalten."
Neru
2

Unter Verwendung der willMove(toParent: UIViewController?)in der folgenden Art und Weise schien für mich an der Arbeit. (Getestet auf iOS12).

override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent);

    if parent == nil
    {
        // View controller is being removed.
        // Perform onDismiss action
    }
}
Shavi
quelle
1

@ user523234 - "Der entlassene ViewViewControllerAnimated im VC1 wurde jedoch nicht aufgerufen."

Sie können nicht davon ausgehen, dass VC1 tatsächlich die Präsentation durchführt - es könnte sich beispielsweise um den Root-View-Controller VC0 handeln. Es sind 3 View Controller beteiligt:

  • sourceViewController
  • PresentingViewController
  • präsentiertViewController

In Ihrem Beispiel VC1 = sourceViewController, VC2 = presentedViewController, ?? = presentingViewController- vielleicht VC1, vielleicht auch nicht.

Sie können sich jedoch immer darauf verlassen, dass VC1.animationControllerForDismissedController aufgerufen wird (wenn Sie die Delegatmethoden implementiert haben), wenn Sie VC2 schließen, und in dieser Methode können Sie mit VC1 tun, was Sie wollen

Andrew Coad
quelle
1

Ich habe diesen Beitrag so oft gesehen, als ich mich mit diesem Problem befasste, dass ich dachte, ich könnte endlich etwas Licht auf eine mögliche Antwort werfen.

Wenn Sie wissen möchten , ob vom Benutzer initiierte Aktionen (wie Gesten auf dem Bildschirm) die Entlassung eines UIActionControllers ausgelöst haben und keine Zeit in die Erstellung von Unterklassen oder Erweiterungen oder was auch immer in Ihrem Code investieren möchten, gibt es eine Alternative.

Wie sich herausstellt, verfügt die Eigenschaft popoverPresentationController eines UIActionControllers (oder vielmehr eines beliebigen UIViewControllers) über einen Delegaten, den Sie jederzeit in Ihrem Code festlegen können , der vom Typ UIPopoverPresentationControllerDelegate ist und über die folgenden Methoden verfügt:

Weisen Sie den Delegaten von Ihrem Aktionscontroller zu, implementieren Sie die Methode (n) Ihrer Wahl in der Delegatenklasse (Ansicht, Ansichtscontroller oder was auch immer) und voila!

Hoffe das hilft.

Izhido
quelle
Und diese sind seit iOS 13 veraltet. Doh
Pronebird
0
  1. Erstellen Sie eine Klassendatei (.h / .m) und nennen Sie sie: DismissSegue
  2. Wählen Sie Unterklasse von: UIStoryboardSegue

  3. Gehen Sie zur Datei DismissSegue.m und notieren Sie den folgenden Code:

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  4. Öffnen Sie das Storyboard und ziehen Sie dann bei gedrückter Strg-Taste von der Schaltfläche "Abbrechen" auf "VC1". Wählen Sie "Aktionssegment" als "Schließen" und Sie sind fertig.

Tapansinh Solanki
quelle
0

Wenn Sie überschreiben, dass der Ansichts-Controller verkleinert wird:

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

Zumindest hat das bei mir funktioniert.

mxcl
quelle
@JohnScalo nicht wahr, einige der "nativen" View-Controller-Hierarchien implementieren sich mit den untergeordneten / übergeordneten Grundelementen.
mxcl
0

overrideing viewDidAppearhat den Trick für mich getan. Ich habe einen Singleton in meinem Modal verwendet und bin nun in der Lage, diesen innerhalb des aufrufenden VC, des Modals und überall sonst einzustellen und zu entfernen.

BEIM
quelle
viewDidAppear?
Dmitry
1
Meinten Sie viewDidDisappear?
Dale
0

Override- viewWillDisappearFunktion im dargestellten View Controller.

override func viewWillDisappear(_ animated: Bool) {
    //Your code here
}
priwiljay
quelle
Ohne Pairing funktioniert es nicht richtig viewDidAppear.
Dmitry
1
Rufen Sie super.viewWillDisappear
Dale
0

Wie bereits erwähnt, ist die Lösung zu verwenden override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil).

Wenn Sie sich fragen, warum override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)dies nicht immer zu funktionieren scheint, stellen Sie möglicherweise fest, dass der Anruf von einem abgefangen wird, UINavigationControllerwenn er verwaltet wird. Ich habe eine Unterklasse geschrieben, die helfen soll:

class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }

Steve
quelle
0

Wenn Sie das Löschen von View Controllern behandeln möchten, sollten Sie den folgenden Code verwenden.

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

Leider können wir die Vervollständigung in der überschriebenen Methode nicht aufrufen, (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;da diese Methode nur aufgerufen wird, wenn Sie die Entlassungsmethode dieses Ansichtscontrollers aufrufen.

Arthur Kvaratskhelija
quelle
Funktioniert aber viewWillDisappearauch nicht richtig ohne Pairing mit viewDidAppear.
Dmitry
viewWillDisappear wird aufgerufen, wenn der VC vollständig abgedeckt ist (z. B. mit einem Modal). Sie wurden möglicherweise nicht entlassen
Lou Franco
0

Eine andere Möglichkeit besteht darin, EntlassungTransitionDidEnd () Ihres benutzerdefinierten UIPresentationControllers abzuhören

Igor Vasilev
quelle
0

Ich habe deinit für den ViewController verwendet

deinit {
    dataSource.stopUpdates()
}

Ein Deinitializer wird unmittelbar vor der Freigabe einer Klasseninstanz aufgerufen.

Dan Alboteanu
quelle