Wie kann ich eine Ansicht von einem UINavigationController aus öffnen und in einem Vorgang durch eine andere ersetzen?

83

Ich habe eine Anwendung, in der ich eine Ansicht vom Stapel eines UINavigationControllers entfernen und durch eine andere ersetzen muss. Die Situation ist, dass die erste Ansicht ein bearbeitbares Element erstellt und sich dann durch einen Editor für das Element ersetzt. Wenn ich die offensichtliche Lösung in der ersten Ansicht mache:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];

Ich bekomme sehr seltsames Verhalten. Normalerweise wird die Editoransicht angezeigt, aber wenn ich versuche, die Schaltfläche "Zurück" in der Navigationsleiste zu verwenden, werden zusätzliche Bildschirme angezeigt, von denen einige leer und andere nur vermasselt sind. Der Titel wird auch zufällig. Es ist, als ob der Navigationsstapel vollständig abgespritzt ist.

Was wäre ein besserer Ansatz für dieses Problem?

Danke, Matt

Matt Brandt
quelle

Antworten:

137

Ich habe festgestellt, dass Sie sich überhaupt nicht manuell mit der viewControllersEigenschaft herumschlagen müssen. Grundsätzlich gibt es 2 knifflige Dinge.

  1. self.navigationControllerwird zurückgegeben, nilwenn selfes sich derzeit nicht auf dem Stapel des Navigationscontrollers befindet. Speichern Sie es also in einer lokalen Variablen, bevor Sie den Zugriff darauf verlieren.
  2. Sie müssen retain(und ordnungsgemäß release) selfoder das Objekt, dem die Methode gehört, in der Sie sich befinden, wird freigegeben, was zu Fremdheit führt.

Sobald Sie diese Vorbereitung durchgeführt haben, können Sie sie wie gewohnt öffnen und drücken. Dieser Code ersetzt sofort den oberen Controller durch einen anderen.

// locally store the navigation controller since
// self.navigationController will be nil once we are popped
UINavigationController *navController = self.navigationController;

// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];

// Pop this controller and replace with another
[navController popViewControllerAnimated:NO];
[navController pushViewController:someViewController animated:NO];

In der letzten Zeile , wenn Sie die Änderung animatedzu YES, dann wird der neue Bildschirm tatsächlich animieren in und der Controller die Sie gerade aufgetaucht animieren heraus. Sieht ziemlich gut aus!

Alex Wayne
quelle
brillant! viel bessere Lösung
Emmby
Genial. Obwohl ich die Autorelease nicht aufrufen musste, funktioniert sie immer noch einwandfrei.
iamj4de
4
Vielleicht eine offensichtliche Ergänzung, aber Sie können dann den obigen Code in einen Animationsblock einfügen, um den Übergang zu animieren: [UIView beginAnimations: @ "View Flip" -Kontext: nil]; [UIView setAnimationDuration: 0.80]; [UIView setAnimationCurve: UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView: navController.view Cache: NO]; [navController pushViewController: newController animiert: JA]; [UIView commitAnimations];
Martin
11
Funktioniert hervorragend mit ARC, indem nur die Retain / Autorelease-Linie entfernt wird.
Ian Terrell
2
@TomerPeled Ja, diese Antwort ist fast 5 Jahre alt ... Ich denke, das war in iOS 3 der Fall. Die APIs haben sich so geändert, dass ich nicht mehr sicher bin, ob es die beste Antwort ist.
Alex Wayne
56

Der folgende Ansatz erscheint mir netter und funktioniert auch gut mit ARC:

UIViewController *newVC = [[UIViewController alloc] init];
// Replace the current view controller
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
[viewControllers removeLastObject];
[viewControllers addObject:newVC];
[[self navigationController] setViewControllers:viewControllers animated:YES];
Luke Rogers
quelle
1
@LukeRogers, dies verursacht für mich die folgende Warnung: Beenden eines Navigationsübergangs in einem unerwarteten Zustand. Der Unteransichtsbaum der Navigationsleiste ist möglicherweise beschädigt. Wie kann man das unterdrücken?
Zaitsman
Mit dieser Lösung überschreiben Sie das Popover. Und um in der Detailansicht zu zeigen, sollte Ihr Code lauten:if(indexPath.row == 0){UIViewController *newVC = [[UIViewController alloc] init];newVC = [self.storyboard instantiateViewControllerWithIdentifier:@"Item1VC"]; NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[_detailViewController.navigationController viewControllers]]; [viewControllers removeLastObject];[viewControllers addObject:newVC]; [_detailViewController.navigationController setViewControllers:viewControllers animated:YES];}
LAOMUSIC ARTS
Was ich gesucht habe.
JERC
9

Aus Erfahrung müssen Sie viewControllersdirekt mit der Eigenschaft des UINavigationController herumspielen . So etwas sollte funktionieren:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

[[self retain] autorelease];
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
self.navigationController.viewControllers = controllers;
[self.navigationController pushViewController:mevc animated: YES];

Hinweis: Ich habe die Aufbewahrung / Freigabe in eine Aufbewahrung / Autorelease geändert, da dies im Allgemeinen nur robuster ist. Wenn zwischen der Aufbewahrung / Freigabe eine Ausnahme auftritt, lecken Sie sich selbst, aber die Autorelease kümmert sich darum.

Lily Ballard
quelle
7

Nach viel Mühe (und dem Optimieren des Codes von Kevin) habe ich endlich herausgefunden, wie das im View Controller geht, der vom Stapel genommen wird. Das Problem, das ich hatte, war, dass self.navigationController null zurückgab, nachdem ich das letzte Objekt aus dem Controller-Array entfernt hatte. Ich denke, es lag an dieser Zeile in der Dokumentation für UIViewController zur Instanzmethode navigationController. "Gibt einen Navigationscontroller nur zurück, wenn sich der View-Controller in seinem Stapel befindet."

Ich denke, sobald der aktuelle Ansichts-Controller vom Stapel entfernt ist, gibt seine navigationController-Methode null zurück.

Hier ist der angepasste Code, der funktioniert:

UINavigationController *navController = self.navigationController;
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
navController.viewControllers = controllers;
[navController pushViewController:mevc animated: YES];

quelle
Das gibt mir ein schwarzes Ganzes!
LAOMUSIC ARTS
4

Danke, das war genau das, was ich brauchte. Ich habe dies auch in eine Animation eingefügt, um die Seitenkräuselung zu erhalten:

        MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

    UINavigationController *navController = self.navigationController;      
    [[self retain] autorelease];

    [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration: 0.7];
    [UIView setAnimationTransition:<#UIViewAnimationTransitionCurlDown#> forView:navController.view cache:NO];

    [navController popViewControllerAnimated:NO];
    [navController pushViewController:mevc animated:NO];

    [UIView commitAnimations];

Die Dauer von 0,6 ist schnell, gut für 3GS und neuer, 0,8 ist für 3G immer noch etwas zu schnell.

Johan

Johan
quelle
Ihr Code ist genau das, was ich verwendet habe, großartig! Vielen Dank. Eine Anmerkung: Mit dem Übergang der Seitenwölbung habe ich unten in der Ansicht ein weißes Kunststück (wer weiß warum), aber mit dem Umdrehen hat es gut funktioniert. Wie auch immer, das ist schöner und kompakter Code!
David H
3

Wenn Sie einen anderen View Controller von popToRootViewController anzeigen möchten, müssen Sie folgende Schritte ausführen:

         UIViewController *newVC = [[WelcomeScreenVC alloc] initWithNibName:@"WelcomeScreenVC" bundle:[NSBundle mainBundle]];
            NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
            [viewControllers removeAllObjects];
            [viewControllers addObject:newVC];
            [[self navigationController] setViewControllers:viewControllers animated:NO];

Jetzt wird der gesamte vorherige Stapel entfernt und ein neuer Stapel mit dem erforderlichen rootViewController erstellt.

msmq
quelle
1

Ich musste kürzlich etwas Ähnliches tun und meine Lösung auf Michaels Antwort basieren. In meinem Fall musste ich zwei View Controller aus dem Navigationsstapel entfernen und dann einen neuen View Controller hinzufügen. Berufung

[Controller removeLastObject];
zweimal, hat in meinem Fall gut funktioniert.

UINavigationController *navController = self.navigationController;

// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];

searchViewController = [[SearchViewController alloc] init];    
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];

[controllers removeLastObject];
// In my case I want to go up two, then push one..
[controllers removeLastObject];
navController.viewControllers = controllers;

NSLog(@"controllers: %@",controllers);
controllers = nil;

[navController pushViewController:searchViewController animated: NO];

mattlangtree
quelle
1

Diese UINavigationControllerInstanzmethode könnte funktionieren ...

Pops View Controller, bis der angegebene View Controller der Top View Controller ist, und aktualisiert dann die Anzeige.

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
Diclophis
quelle
1

Hier ist ein weiterer Ansatz, bei dem nicht direkt mit dem viewControllers-Array herumgespielt werden muss. Überprüfen Sie, ob der Controller bereits geöffnet wurde, und drücken Sie ihn, wenn dies der Fall ist.

TasksViewController *taskViewController = [[TasksViewController alloc] initWithNibName:nil bundle:nil];

if ([navigationController.viewControllers indexOfObject:taskViewController] == NSNotFound)
{
    [navigationController pushViewController:taskViewController animated:animated];
}
else
{
    [navigationController popToViewController:taskViewController animated:animated];
}
ezekielDFM
quelle
1
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;
Ravi
quelle
Dies verursacht eine Warnung für mich in der Konsole - Beenden eines Navigationsübergangs in einem unerwarteten Zustand. Der Unteransichtsbaum der Navigationsleiste ist möglicherweise beschädigt. Wie kann man das unterdrücken?
Zaitsman
1

Am liebsten mache ich das mit einer Kategorie auf UINavigationController. Folgendes sollte funktionieren:

UINavigationController + Helpers.h #import

@interface UINavigationController (Helpers)

- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller;

@end

UINavigationController + Helpers.m
#import "UINavigationController + Helpers.h"

@implementation UINavigationController (Helpers)

- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller {
    UIViewController* topController = self.viewControllers.lastObject;
    [[topController retain] autorelease];
    UIViewController* poppedViewController = [self popViewControllerAnimated:NO];
    [self pushViewController:controller animated:NO];
    return poppedViewController;
}

@end

Dann können Sie von Ihrem Ansichts-Controller aus die Draufsicht durch eine neue ersetzen, indem Sie Folgendes tun:

[self.navigationController replaceTopViewControllerWithViewController: newController];
bbrame
quelle
0

Sie können mit dem Array der Navigationsansichts-Controller überprüfen, welches Sie Ihnen alle Ansichts-Controller geben, die Sie im Navigationsstapel hinzugefügt haben. Mit diesem Array können Sie zurück zu einem bestimmten Ansichts-Controller navigieren.

Jignesh
quelle
0

Für Monotouch / Xamarin IOS:

innerhalb der UISplitViewController-Klasse;

UINavigationController mainNav = this._navController; 
//List<UIViewController> controllers = mainNav.ViewControllers.ToList();
mainNav.ViewControllers = new UIViewController[] { }; 
mainNav.PushViewController(detail, true);//to have the animation
Nabil.A
quelle
0

Alternative,

Sie können verwendet werden, categoryum zu vermeiden self.navigationControllersein nilnachpopViewControllerAnimated

einfach pop and push, es ist leicht zu verstehen, muss nicht zugreifen viewControllers....

// UINavigationController+Helper.h
@interface UINavigationController (Helper)

- (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated;

@end


// UINavigationController+Helper.m
@implementation UINavigationController (Helper)

- (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    UIViewController *v =[self popViewControllerAnimated:NO];

    [self pushViewController:viewController animated:animated];

    return v;
}
@end

In Ihrem ViewController

// #import "UINavigationController+Helper.h"
// invoke in your code
UIViewController *v= [[MyNewViewController alloc] init];

[self.navigationController popThenPushViewController:v animated:YES];

RELEASE_SAFELY(v);
payliu
quelle
0

Nicht genau die Antwort, könnte aber in einigen Szenarien hilfreich sein (meine zum Beispiel):

Wenn Sie den Viewcontroller C öffnen und zu B (außerhalb des Stapels) anstatt zu A (der eine unter C) wechseln müssen, können Sie B vor C drücken und alle 3 auf dem Stapel haben. Indem Sie den B-Push unsichtbar halten und auswählen, ob nur C oder C und B insgesamt eingeblendet werden sollen, können Sie den gleichen Effekt erzielen.

anfängliches Problem A -> C (Ich möchte C platzen lassen und B aus dem Stapel zeigen)

mögliche Lösung A -> B (unsichtbar gedrückt) -> C (wenn ich C platziere, entscheide ich mich, B anzuzeigen oder es auch zu platzen)

Alasker
quelle
0

Ich benutze diese Lösung, um die Animation zu behalten.

[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];
code4j
quelle