Anstatt zu drücken, wie man den View Controller ersetzt (oder vom Navigationsstapel entfernt)?

68

Ich habe eine kleine iPhone-App , die einen Navigationscontroller verwendet, um 3 Ansichten anzuzeigen (hier Vollbild ):

Xcode Screenshot

Zunächst wird eine Liste der sozialen Netzwerke (Facebook, Google+ usw.) angezeigt:

Screenshot auflisten

Anschließend wird ein OAuth-Dialogfeld angezeigt, in dem Sie nach Anmeldeinformationen gefragt werden:

Screenshot anmelden

Und (danach ebenfalls UIWebView) für Berechtigungen:

Berechtigungs-Screenshot

Schließlich wird der letzte Ansichts-Controller mit Benutzerdetails angezeigt (in der realen App ist dies das Menü, in dem das Multiplayer-Spiel gestartet werden kann):

Details Screenshot

Das alles funktioniert gut, aber ich habe ein Problem, wenn der Benutzer zurückgehen und ein anderes soziales Netzwerk auswählen möchte:

Der Benutzer berührt die Zurück-Schaltfläche und anstatt die erste Ansicht anzuzeigen, wird die zweite angezeigt, in der er erneut nach OAuth-Anmeldeinformationen / Berechtigungen fragt.

Was kann ich hier machen? Xcode 5.0.2 zeigt eine sehr begrenzte Auswahl für Segmente - Push , Modal (was ich nicht verwenden kann, da es die für mein Spiel benötigte Navigationsleiste verdeckt) und Benutzerdefiniert .

Ich bin ein Neuling in der iOS-Programmierung, aber früher habe ich eine mobile Adobe AIR-App entwickelt. Dort war es möglich, 1) die Ansicht zu ersetzen, anstatt zu drücken, und 2) eine nicht benötigte Ansicht aus dem Navigationsstapel zu entfernen.

Wie mache ich das bitte in einer nativen App?

Alexander Farber
quelle

Antworten:

38

Sie können einen benutzerdefinierten Abschnitt verwenden: Dazu müssen Sie eine Klasse mit der Unterklasse UIStoryboardSegue (Beispiel MyCustomSegue) erstellen und dann die "Leistung" mit so etwas überschreiben

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    // Pop to root view controller (not animated) before pushing
    [navigationController popToRootViewControllerAnimated:NO];
    [navigationController pushViewController:destinationController animated:YES];    
}

Gehen Sie zu diesem Zeitpunkt zu Interface Builder, wählen Sie "Benutzerdefiniert" und geben Sie den Namen Ihrer Klasse ein (Beispiel MyCustomSegue).

Daniele
quelle
Vielen Dank, aber sind Sie sicher, dass es möglich ist , die 2. Szene durch die 3. Szene zu ersetzen, anstatt zu drücken, damit der Benutzer durch Berühren der Schaltfläche Zurück zur 1. Szene zurückkehrt, indem Sie einen benutzerdefinierten Abschnitt verwenden? Ich lese developer.apple.com/library/ios/documentation/UIKit/Reference/… und kann einfach nicht herausfinden, wie es geht.
Alexander Farber
1
In Ihrem ersten Vorschlag haben Sie nicht sourceViewController.navigationControllerin einer Variablen gespeichert und es wurde nilvor dem letzten Befehl eingegeben perform- und deshalb hat es bei mir nicht funktioniert. Aber die aktuelle Version funktioniert perfekt, danke! Die Antworten von Lauro und Bhavya sind ebenfalls sehr aufschlussreich.
Alexander Farber
@ Daniele. Woher haben Sie einen Verweis auf destinationViewController?
Zulkarnain Shah
Während es funktioniert, sieht es seltsam aus, da die Animation zum Ziel von rootViewController animiert wird, sodass der Story-Flow unterbrochen wird.
Teddy
59

Um die verschiedenen oben genannten Bereiche zu erweitern, ist dies meine Lösung. Es hat folgende Vorteile:

  • Kann überall im Ansichtsstapel funktionieren, nicht nur in der Draufsicht (nicht sicher, ob dies realistisch jemals benötigt wird oder sogar technisch möglich ist, aber hey, es ist da drin).
  • Es wird kein Pop-OR-Übergang zum vorherigen Ansichts-Controller verursacht, bevor der Ersatz angezeigt wird. Es wird lediglich der neue Controller mit einem natürlichen Übergang angezeigt, wobei die Rückennavigation zur gleichen Rückwärtsnavigation des Quell-Controllers erfolgt.

Segue Code:

- (void)perform {
    // Grab Variables for readability
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;

    // Get a changeable copy of the stack
    NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
    // Replace the source controller with the destination controller, wherever the source may be
    [controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];

    // Assign the updated stack with animation
    [navigationController setViewControllers:controllerStack animated:YES];
}
ima747
quelle
Nett! Wie wäre es mit einem Pop-Übergang (oder einer Verschiebung nach rechts), wenn Sie ein "vorheriges" anstelle eines "nächsten" Szenarios ausführen?
Finneycanhelp
Ich bin mir nicht sicher, ob ich wirklich weiß, was Sie fragen, aber Sie können den Stapel so ändern, dass die vorherige Ansicht
eingefügt
26

Das benutzerdefinierte Segment funktionierte bei mir nicht, da ich einen Splash View-Controller hatte und diesen ersetzen wollte. Da es nur einen Ansichts-Controller in der Liste gab, popToRootViewControllerließ der Splash immer noch auf dem Stapel. Ich habe den folgenden Code verwendet, um den einzelnen Controller zu ersetzen

-(void)perform {
    UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
    UIViewController *destinationController = (UIViewController*)[self destinationViewController];
    UINavigationController *navigationController = sourceViewController.navigationController;
    [navigationController setViewControllers:@[destinationController] animated:YES];
}

und jetzt in Swift 4:

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        source.navigationController?.setViewControllers([destination], animated: true)
    }
}

und jetzt in Swift 2.0

class ReplaceSegue: UIStoryboardSegue {

    override func perform() {
        sourceViewController.navigationController?.setViewControllers([destinationViewController], animated: true)
    }
}
Christophercotton
quelle
Ich habe ein Hamburger-Menü als NavigationViewController. Ihr Code funktioniert, ersetzt jedoch die in meinem Menü verwendete tableView anstelle des viewController (root).
Paul Bénéteau
Interessantes @greenpoisononeTV Auf welchem ​​Controller rufen Sie "performSegue" auf? Da der sourceViewController und der Zielcontroller mit dem Segue gesetzt sind.
Christophercotton
Ich habe mit Strg + Ziehen einen Übergang von einem meiner tableViewCellzu einem erstellt viewController. Der Übergang wird mit der ReplaceSegueKlasse als benutzerdefiniert festgelegt .
Paul Bénéteau
18

die schnelle 2 version von ima747 antwort:

override func perform() {
    let navigationController: UINavigationController = sourceViewController.navigationController!;

    var controllerStack = navigationController.viewControllers;
    let index = controllerStack.indexOf(sourceViewController);
    controllerStack[index!] = destinationViewController

    navigationController.setViewControllers(controllerStack, animated: true);
}

Wie er erwähnte, hat es die folgenden Vorteile:

  • Kann überall im Ansichtsstapel funktionieren, nicht nur in der Draufsicht (nicht sicher, ob dies realistisch jemals benötigt wird oder sogar technisch möglich ist, aber hey, es ist da drin).
  • Es wird kein Pop-OR-Übergang zum vorherigen Ansichts-Controller verursacht, bevor der Ersatz angezeigt wird. Es wird lediglich der neue Controller mit einem natürlichen Übergang angezeigt, wobei die Rückennavigation zur gleichen Rückwärtsnavigation des Quell-Controllers erfolgt.
Dr.Agos
quelle
6
Um die if letSicherheit zu erhöhen , können Sie den Index unter bestimmten Bedingungen abrufen. Auf diese Weise müssen Sie die Variable nicht zwangsweise auspacken, was möglicherweise fehleranfällig ist.
Afonso
Wann genau wird der sourceViewController mit dieser Implementierung vom Stapel genommen?
Zulkarnain Shah
Woher haben Sie auch einen Verweis auf destinationViewController?
Zulkarnain Shah
17

Für dieses Problem denke ich, dass die Antwort einfach ist

  1. Rufen Sie das Array der View Controller von NavigationController ab
  2. Entfernen des letzten ViewControllers (aktueller View Controller)
  3. Fügen Sie endlich einen neuen ein
  4. Setzen Sie dann das Array von ViewControllern wie folgt auf den Navigationscontroller zurück:

     if let navController = self.navigationController {
        let newVC = DestinationViewController(nibName: "DestinationViewController", bundle: nil)
    
        var stack = navController.viewControllers
        stack.remove(at: stack.count - 1)       // remove current VC
        stack.insert(newVC, at: stack.count) // add the new one
        navController.setViewControllers(stack, animated: true) // boom!
     }
    

funktioniert perfekt mit Swift 3.

Hoffe, es hilft für einige neue Leute.

Prost.

Xuan-Gieng Nguyen
quelle
Was ist DestinationViewController?
Paul Bénéteau
Dies ist der Ansichts-Controller, zu dem Sie navigieren möchten.
Rameswar Prasad
Ich kann meine Ziel-VC nicht mit diesem Code laden. Stürzt ab. Zu welcher Methode fügen Sie diesen Code hinzu?
Zulkarnain Shah
9

Die Abwicklung wäre die am besten geeignete Lösung für dieses Problem. Ich stimme Lauro zu.

Hier ist eine kurze Erklärung zum Einrichten eines Abwicklungsabschnitts von detailsViewController [oder viewController3] zu myAuthViewController [oder viewController1].

Dies ist im Wesentlichen die Vorgehensweise beim Abwickeln des Codes.

  • Implementieren Sie eine IBAction-Methode in dem viewController, zu dem Sie sich abwickeln möchten (in diesem Fall viewController1). Der Methodenname kann beliebig lang sein, sodass ein Argument vom Typ UIStoryboardSegue erforderlich ist.

    @IBAction func unwindToMyAuth(segue: UIStoryboardSegue) {
    println("segue with ID: %@", segue.Identifier)
    }
    
  • Verknüpfen Sie diese Methode mit dem viewController (3), von dem Sie sich entspannen möchten. Klicken Sie zum Verknüpfen mit der rechten Maustaste (Doppeltipp) auf das Exit-Symbol oben im viewController. Zu diesem Zeitpunkt wird die Methode 'unwindToMyAuth' im Popup-Feld angezeigt. Klicken Sie bei gedrückter Ctrl-Taste von dieser Methode auf das erste Symbol, das viewController-Symbol (ebenfalls oben im viewController in derselben Zeile wie das Exit-Symbol). Wählen Sie die Option 'Manuell', die angezeigt wird.

  • Wählen Sie in der Dokumentübersicht für dieselbe Ansicht (viewController3) den soeben erstellten Abwicklungsbereich aus. Gehen Sie zum zugewiesenen Inspektor und weisen Sie eine eindeutige Kennung für diesen Abwicklungsabschnitt zu. Wir haben jetzt einen generischen Abwicklungsbereich zur Verwendung bereit.

  • Jetzt kann der Abwicklungsabschnitt wie jeder andere Abschnitt aus dem Code ausgeführt werden.

    performSegueWithIdentifier("unwind.to.myauth", sender: nil)
    

Dieser Ansatz führt Sie von viewController3 zu viewController1, ohne dass viewController2 aus der Navigationshierarchie entfernt werden muss.

Im Gegensatz zu anderen Segmenten instanziieren Abwicklungssegmente keinen Ansichts-Controller, sondern gehen nur zu einem vorhandenen Ansichts-Controller in der Navigationshierarchie.

Lester
quelle
5

Wie in den vorherigen Antworten erwähnt, sieht es nicht sehr gut aus, wenn Pop nicht animiert und dann animiert gedrückt wird, da der Benutzer den tatsächlichen Prozess sieht. Ich empfehle Ihnen, zuerst animiert zu drücken und dann den vorherigen VC zu entfernen. Wie so:

extension UINavigationController {
    func replaceCurrentViewController(with viewController: UIViewController, animated: Bool) {
        pushViewController(viewController, animated: animated)
        let indexToRemove = viewControllers.count - 2
        if indexToRemove >= 0 {
            viewControllers.remove(at: indexToRemove)
        }
    }
}
Alex Shubin
quelle
1
Danke für die Antwort, es funktioniert wie ein Zauber. Ich liebe die Idee der Erweiterung
Murat Akdeniz
5

Hier eine schnelle Swift 4/5-Lösung, indem eine benutzerdefinierte Sequenz erstellt wird, die den Ansichtscontroller (oberster Stapel) durch den neuen ersetzt (ohne Animation):

class SegueNavigationReplaceTop: UIStoryboardSegue {

    override func perform () {
        guard let navigationController = source.navigationController else { return }
        navigationController.popViewController(animated: false)
        navigationController.pushViewController(destination, animated: false)
    }
}
HixField
quelle
2

Verwenden Sie den Code für die letzte Ansicht des folgenden Codes. Sie können eine andere Schaltfläche verwenden oder eine eigene Schaltfläche anstelle der von mir verwendeten Schaltfläche zum Abbrechen verwenden

- (void)viewDidLoad
{
 [super viewDidLoad];

 [self.navigationController setNavigationBarHidden:YES];

 UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismiss:)];

self.navigationItemSetting.leftBarButtonItem = cancelButton;

}

- (IBAction)dismissSettings:(id)sender
{

// your logout code for social media selected
[self.navigationController popToRootViewControllerAnimated:YES];

}
Bhavya Kothari
quelle
Du meinst wahrscheinlich setNavigationBarHidden:NO? Ihr Code funktioniert gut, danke und es ist auch ein guter Ort für den Abmeldecode des sozialen Netzwerks.
Alexander Farber
2

Was Sie wirklich tun sollten, ist modal zu präsentieren, UINavigationControllerdas das soziale Netzwerk UIViewControllersüber Ihrem Menü enthält UIViewController(das in ein eingebettet werden kann, UINavigationControllerwenn Sie möchten). Sobald sich ein Benutzer authentifiziert hat, schließen Sie das soziale Netzwerk UINavigationControllerund zeigen Ihr Menü UIViewControllererneut an.

Kennzeichen
quelle
2

In swift3Erstellen einer segue -add Identifikator -add und SET in segue (Storyboard) benutzerdefinierten Klasse von Storyboard Cocoatouch Datei -In benutzerdefinierte Klasse Überschreibung durchführen ()

override func perform() {
    let sourceViewController = self.source
    let destinationController = self.destination
    let navigationController = sourceViewController.navigationController
    // Pop to root view controller (not animated) before pushing
    if self.identifier == "your identifier"{
    navigationController?.popViewController(animated: false)
    navigationController?.pushViewController(destinationController, animated: true)
    }
    }

-Sie müssen auch eine Methode in Ihrem Quellansichtscontroller überschreiben

  override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
    return false
}
Mahesh Giri
quelle
1

Nun, was Sie auch tun können, ist die Verwendung des Unwind View Controller-Materials.

Eigentlich denke ich, dass dies genau das ist, was Sie brauchen.

Überprüfen Sie diesen Eintrag: Wofür sind Unwind-Segmente und wie verwenden Sie sie?

Lauro Wolff Valente Sobrinho
quelle
1
Ich glaube nicht, dass Sie mit Abwicklungssegmenten das gewünschte Verhalten erzielen können, da Abwicklungen nur zum Zurückgehen gedacht sind. Ein Austausch erfolgt im Wesentlichen in einem Schritt zurück zu einem anderen Controller.
ima747
Die Frage lautet: "Wie gehe ich direkt von Ansicht 3 zu Ansicht 1 zurück?" Und nicht wieder vorwärts.
zcui93
1

Das hat bei mir in Swift 3 funktioniert:

class ReplaceSegue: UIStoryboardSegue {
    override func perform() {
        if let navVC = source.navigationController {
            navVC.pushViewController(destination, animated: true)
        } else {
            super.perform()
        }
    }
}
jeffbailey
quelle
1

Wie wäre es damit :) Ich jetzt ist es eine alte Frage, aber das wird als Zauber wirken:

UIViewController *destinationController = [[UIViewController alloc] init];
UINavigationController *newNavigation = [[UINavigationController alloc] init];
[newNavigation setViewControllers:@[destinationController]];
[[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation;
Denis Kozhukhov
quelle