web-dev-qa-db-de.com

modal View Controller - Anzeigen und Löschen von

Ich breche mir den Kopf für die letzte Woche, wie das Problem gelöst werden kann, indem mehrere Ansichtssteuerungen angezeigt und verworfen werden. Ich habe ein Beispielprojekt erstellt und den Code direkt aus dem Projekt eingefügt. Ich habe 3 Ansichtscontroller mit ihren entsprechenden .xib-Dateien. MainViewController, VC1 und VC2. Ich habe zwei Tasten auf dem Hauptansicht-Controller.

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

Dies öffnet VC1 ohne Probleme. In VC1 habe ich eine weitere Schaltfläche, die VC2 öffnen und gleichzeitig VC1 verwerfen sollte.

- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
    VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
    [vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc2 animated:YES completion:nil];
    [self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!


- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1. 

Ich möchte, dass er zum Main View Controller zurückkehrt, während VC1 zur gleichen Zeit aus dem Speicher genommen werden sollte. VC1 sollte nur angezeigt werden, wenn ich auf die Schaltfläche VC1 am Hauptcontroller klicke.

Die andere Schaltfläche des Main View Controllers sollte auch VC2 direkt anzeigen können, indem VC1 umgangen wird, und sollte zum Hauptcontroller zurückkehren, wenn auf VC2 eine Schaltfläche geklickt wird. Es gibt keinen lang laufenden Code, Schleifen oder Timer. Nur bloße Knochenaufrufe zum Anzeigen von Controllern.

79
Hema

Diese Linie:

[self dismissViewControllerAnimated:YES completion:nil];

sendet keine Nachricht an sich selbst, sondern sendet tatsächlich eine Nachricht an die präsentierende VC und fordert sie auf, die Entlassung vorzunehmen. Wenn Sie eine VC präsentieren, erstellen Sie eine Beziehung zwischen der präsentierenden VC und der präsentierten. Sie sollten also das präsentierende VC nicht zerstören, während es präsentiert wird (das präsentierte VC kann diese Entlassungsnachricht nicht zurücksenden ...). Da Sie dies nicht wirklich berücksichtigen, verlassen Sie die App in einem verwirrten Zustand. Siehe meine Antwort Einen Presented View Controller schließen in der ich empfehle, dass diese Methode klarer geschrieben ist:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

In Ihrem Fall müssen Sie sicherstellen, dass das gesamte Controlling in mainVC erfolgt. Sie sollten einen Delegaten verwenden, um die richtige Nachricht von ViewController1 an MainViewController zurückzusenden, damit mainVC VC1 schließen und dann VC2 präsentieren kann.

Im VC2 VC1 füge ein Protokoll in deine .h-Datei über der @ -Schnittstelle hinzu:

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

und weiter unten in derselben Datei im Abschnitt @interface deklarieren Sie eine Eigenschaft, die den Delegatenzeiger enthält:

@property (nonatomic,weak) id <ViewController1Protocol> delegate;

In der VC1-M-Datei sollte die Methode der Schaltfläche zum Schließen die Delegatmethode aufrufen

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}

Legen Sie es jetzt in mainVC als VC1-Delegaten fest, wenn Sie VC1 erstellen:

- (IBAction)present1:(id)sender {
    ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    vc.delegate = self;
    [self present:vc];
}

und implementieren Sie die Delegate-Methode:

- (void)dismissAndPresent2 {
    [self dismissViewControllerAnimated:NO completion:^{
        [self present2:nil];
    }];
}

present2: kann dieselbe Methode sein wie die IBAction-Methode für die Schaltfläche VC2Pressed:. Beachten Sie, dass es vom Beendigungsblock aufgerufen wird, um sicherzustellen, dass VC2 nicht präsentiert wird, bis VC1 vollständig entlassen wurde.

Sie wechseln jetzt von VC1-> VCMain-> VC2, sodass wahrscheinlich nur einer der Übergänge animiert werden soll.

update

In Ihren Kommentaren äußern Sie sich überrascht über die Komplexität, die erforderlich ist, um eine scheinbar einfache Sache zu erreichen. Ich kann Ihnen versichern, dass dieses Delegationsmuster für einen Großteil von Objective-C und Cocoa von so zentraler Bedeutung ist. Dieses Beispiel ist so einfach, dass Sie sich wirklich die Mühe machen sollten, sich damit vertraut zu machen.

In Apples View Controller Programming Guide haben sie dies zu sagen :

Einen Presented View Controller schließen

Wenn es Zeit ist, einen Controller für präsentierte Ansichten zu schließen, besteht der bevorzugte Ansatz darin, ihn vom Controller für präsentierte Ansichten schließen zu lassen. Mit anderen Worten, wann immer möglich, sollte derselbe Ansichtscontroller, der den Ansichtscontroller präsentiert hat, auch die Verantwortung dafür übernehmen, ihn zu schließen. Obwohl es verschiedene Techniken gibt, um den Presenting View Controller zu benachrichtigen, dass sein Presenting View Controller verworfen werden sollte, ist die Delegation die bevorzugte Technik. Weitere Informationen finden Sie unter "Verwenden der Delegierung zur Kommunikation mit anderen Controllern".

Wenn Sie wirklich überlegen, was Sie erreichen möchten und wie Sie vorgehen, werden Sie feststellen, dass das Versenden von Nachrichten an Ihren MainViewController für die gesamte Arbeit der einzig logische Ausweg ist, da Sie keinen Navigationscontroller verwenden möchten. Wenn Sie do einen NavController verwenden, delegieren Sie tatsächlich, auch wenn nicht ausdrücklich, die gesamte Arbeit an den navController. Es muss ein some Objekt geben, das einen zentralen Überblick darüber gibt, was mit Ihrer VC Navigation passiert, und Sie benötigen some Kommunikationsmethode, was auch immer Sie tun.

In der Praxis ist der Rat von Apple ein wenig extrem. In normalen Fällen müssen Sie keinen dedizierten Delegaten und keine Methode festlegen. Sie können sich auf [self presentingViewController] dismissViewControllerAnimated: verlassen andere Effekte auf entfernte Objekte haben, die Sie beachten müssen.

Hier ist etwas, das Sie vorstellen können , um ohne den ganzen Ärger der Delegierten zu arbeiten ...

- (IBAction)dismiss:(id)sender {
    [[self presentingViewController] dismissViewControllerAnimated:YES 
                                                        completion:^{
        [self.presentingViewController performSelector:@selector(presentVC2:) 
                                            withObject:nil];
    }];

}

Nachdem der präsentierende Controller aufgefordert wurde, uns zu entlassen, haben wir einen Beendigungsblock, der eine Methode im präsentierendenViewController aufruft, um VC2 aufzurufen. Kein Delegierter benötigt. (Ein großes Verkaufsargument von Blöcken ist, dass sie unter diesen Umständen den Bedarf an Delegierten verringern.) In diesem Fall stehen jedoch ein paar Dinge im Weg ...

  • in VC1 haben Sie nicht know dass mainVC die Methode present2 implementiert - es kann zu schwer zu debuggenden Fehlern oder Abstürzen kommen. Delegierte helfen Ihnen, dies zu vermeiden.
  • sobald VC1 entlassen wurde, ist es nicht mehr so ​​weit, den Abschlussblock auszuführen ... oder? Bedeutet self.presentingViewController noch etwas? Sie wissen es nicht (und ich auch nicht) ... mit einem Delegierten haben Sie diese Unsicherheit nicht.
  • Wenn ich versuche, diese Methode auszuführen, bleibt sie ohne Warnung oder Fehler hängen.

Also bitte ... nehmen Sie sich Zeit, um die Delegation zu lernen!

update2

In Ihrem Kommentar haben Sie es geschafft, dass es funktioniert, indem Sie dies im Dismiss Button Handler von VC2 verwenden:

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

Das ist sicherlich viel einfacher, aber es gibt eine Reihe von Problemen.

Enge Kopplung
Sie haben Ihre viewController-Struktur fest miteinander verbunden. Wenn Sie beispielsweise einen neuen viewController vor mainVC einfügen, wird Ihr erforderliches Verhalten beeinträchtigt (Sie würden zum vorherigen navigieren). In VC1 mussten Sie auch VC2 importieren. Daher gibt es eine Reihe von Abhängigkeiten, die die OOP/MVC-Ziele sprengen.

Bei der Verwendung von Delegierten müssen weder VC1 noch VC2 etwas über mainVC oder dessen Vorgänger wissen, sodass wir alles lose gekoppelt und modular halten.

Memory
VC1 ist nicht weggegangen, Sie haben noch zwei Zeiger darauf:

  • die Eigenschaft presentedViewController von mainVC
  • Die Eigenschaft presentingViewController von VC2

Sie können dies testen, indem Sie sich anmelden und dies auch nur von VC2 aus tun

[self dismissViewControllerAnimated:YES completion:nil]; 

Es funktioniert immer noch und bringt dich zurück zu VC1.

Das scheint mir ein Speicherleck zu sein.

Der Hinweis darauf ist in der Warnung zu finden, die Sie hier erhalten:

[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
 // Attempt to dismiss from view controller <VC1: 0x715e460>
 // while a presentation or dismiss is in progress!

Die Logik bricht zusammen, wenn Sie versuchen, die präsentierende VC von denen VC2 die präsentierte VC ist. Die zweite Nachricht wird nicht wirklich ausgeführt - nun, vielleicht passiert etwas, aber Sie haben immer noch zwei Zeiger auf ein Objekt, von dem Sie dachten, Sie hätten es entfernt. ( edit - Ich habe dies überprüft und es ist nicht so schlimm, beide Objekte verschwinden, wenn Sie zurück zu mainVC gelangen )

Das ist eine ziemlich langatmige Art zu sagen - bitte setzen Sie Delegierte ein. Wenn es hilft, habe ich das Muster hier noch einmal kurz beschrieben:
Ist das Übergeben eines Controllers an einem Konstrukteur immer eine schlechte Übung?

Update 3
Wenn Sie Delegierten wirklich aus dem Weg gehen möchten, ist dies der beste Weg:

In VC1:

[self presentViewController:VC2
                   animated:YES
                 completion:nil];

Aber nicht entlassen ... wie wir festgestellt haben, passiert es sowieso nicht wirklich.

In VC2:

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];

Da wir (wissen), dass wir VC1 nicht entlassen haben, können wir bis VC1 bis MainVC zurückrufen. MainVC entlässt VC1. Da VC1 nicht mehr vorhanden ist, wird es auch von VC2 unterstützt, sodass Sie in einem sauberen Zustand wieder bei MainVC sind.

Es ist immer noch stark gekoppelt, da VC1 über VC2 Bescheid wissen muss und VC2 wissen muss, dass es über MainVC-> VC1 erreicht wurde, aber es ist das Beste, was Sie ohne ein bisschen explizite Delegierung erreichen können.

188
foundry

Beispiel in Swift, wobei oben die Erklärung der Gießerei und die Dokumentation von Apple dargestellt werden:

  1. Basierend auf der Dokumentation von Apple und der Gießerei -Erklärung oben (Korrektur einiger Fehler) wird die Version von presentViewController Mit einem Delegate-Entwurfsmuster verwendet:

ViewController.Swift

import UIKit

protocol ViewControllerProtocol {
    func dismissViewController1AndPresentViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
        self.presentViewController(vc1, animated: true, completion: nil)
    }

    func dismissViewController1AndPresentViewController2() {
        self.dismissViewControllerAnimated(false, completion: { () -> Void in
            let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
            self.presentViewController(vc2, animated: true, completion: nil)
        })
    }

}

ViewController1.Swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.dismissViewController1AndPresentViewController2()
    }

}

ViewController2.Swift

import UIKit

class ViewController2: UIViewController {

}
  1. Basierend auf der obigen Erklärung des Herstellers (Korrektur einiger Fehler) wird die Version von __ pushViewController unter Verwendung eines Delegatenentwurfsmusters verwendet:

ViewController.Swift

import UIKit

protocol ViewControllerProtocol {
    func popViewController1AndPushViewController2()
}

class ViewController: UIViewController, ViewControllerProtocol {

    @IBAction func goToViewController1BtnPressed(sender: UIButton) {
        let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
        vc1.delegate = self
        self.navigationController?.pushViewController(vc1, animated: true)
    }

    func popViewController1AndPushViewController2() {
        self.navigationController?.popViewControllerAnimated(false)
        let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
        self.navigationController?.pushViewController(vc2, animated: true)
    }

}

ViewController1.Swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.popViewController1AndPushViewController2()
    }

}

ViewController2.Swift

import UIKit

class ViewController2: UIViewController {

}
9
King-Wizard

Ich denke, Sie haben einige grundlegende Konzepte zu iOS-Modal-View-Controllern missverstanden. Wenn Sie VC1 abweisen, werden auch alle von VC1 präsentierten View-Controller verworfen. Apple ist für modale View-Controller vorgesehen, die gestapelt fließen sollen - in Ihrem Fall wird VC2 von VC1 präsentiert. Sie lehnen VC1 ab, sobald Sie VC2 von VC1 präsentieren, so dass es ein totales Chaos ist .. Um das zu erreichen, was Sie wollen, sollte sich buttonPressedFromVC1 der MainVC VC2 sofort nach VC1 selbst entlassen lassen. Und ich denke, das kann ohne Delegierte erreicht werden. Etwas entlang der Linien:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

Beachten Sie, dass self.presentingViewController in einer anderen Variablen gespeichert ist, da Sie sich nach dem Abbruch von vc1 nicht mehr darauf beziehen sollten.

9
Radu Simionescu

Radu Simionescu - tolle Arbeit! und darunter Ihre Lösung für Swift-Liebhaber:

@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) {
    let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it
    var presentingVC = self.presentingViewController
    self.dismissViewControllerAnimated(false, completion: { () -> Void   in
        presentingVC!.presentViewController(secondViewController, animated: true, completion: nil)
    })
}
4
chrisco

Ich wollte das:

MapVC ist eine Karte im Vollbildmodus. 

Wenn ich eine Taste drücke, wird PopupVC (nicht im Vollbildmodus) oberhalb der Karte geöffnet.

Wenn ich in PopupVC eine Taste drücke, kehrt sie zu MapVC zurück, und ich möchte viewDidAppear ausführen.

Ich tat dies:

MapVC.m: In der Schaltflächenaktion wird ein Programm programmgesteuert und ein Delegat festgelegt

- (void) buttonMapAction{
   PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"];
   popvc.delegate = self;
   [self presentViewController:popvc animated:YES completion:nil];
}

- (void)dismissAndPresentMap {
  [self dismissViewControllerAnimated:NO completion:^{
    NSLog(@"dismissAndPresentMap");
    //When returns of the other view I call viewDidAppear but you can call to other functions
    [self viewDidAppear:YES];
  }];
}

PopupVC.h: Fügen Sie vor @interface das Protokoll hinzu

@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end

nach @interface eine neue Eigenschaft

@property (nonatomic,weak) id <PopupVCProtocol> delegate;

PopupVC.m: 

- (void) buttonPopupAction{
  //jump to dismissAndPresentMap on Map view
  [self.delegate dismissAndPresentMap];
}
0
Mer

Ich habe das Problem durch die Verwendung von UINavigationController beim Präsentieren von ... gelöst. In MainVC beim Präsentieren von VC1

let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)

Wenn ich in VC1 VC2 anzeigen und gleichzeitig VC1 (nur eine Animation) verwerfen möchte, kann ich eine Push-Animation per machen 

let vc2 = VC2()
self.navigationController?.setViewControllers([vc2], animated: true)

Wenn Sie in VC2 den View-Controller schließen, können Sie wie üblich Folgendes verwenden:

self.dismiss(animated: true, completion: nil)
0
Duong Ngo