How to pop back to root view controller but then p

2019-03-15 05:44发布

问题:

I am writing a simple application that has 3 view controllers. The root view controller is an item listing, basic table view. Off of this view controller, I push two different view controllers based on some user interaction - a create item view controller or a view item view controller.

So, the storyboard segues just look like a V, or something.

On my create item view controller, I would like it to pop back to the root view controller when the user creates a new item, but then push to the view item controller so that I can look at the newly created item.

I can't seem to get this to work. It's easy enough to pop back to the root view controller, but I'm unable to push that view item controller.

Any ideas? I've pasted my code, below. The pop function works, but the new view never appears.

- (void) onSave:(id)sender {

    CLLocation *currentLocation = [[LocationHelper sharedInstance] currentLocation];

    // format the thread object dictionary
    NSArray* location = @[ @(currentLocation.coordinate.latitude), @(currentLocation.coordinate.longitude) ];
    NSDictionary* thread = @{ @"title": _titleField.text, @"text": _textField.text, @"author": @"mustached-bear", @"location": location };

    // send the new thread to the api server
    [[DerpHipsterAPIClient sharedClient] postPath:@"/api/thread"
                                       parameters:thread
                                          success:^(AFHTTPRequestOperation *operation, id responseObject) {

                                              // init thread object
                                              Thread *thread = [[Thread alloc] initWithDictionary:responseObject];

                                              // init view thread controller
                                              ThreadViewController *viewThreadController = [[ThreadViewController alloc] init];
                                              viewThreadController.thread = thread;

                                              [self.navigationController popToRootViewControllerAnimated:NO];
                                              [self.navigationController pushViewController:viewThreadController animated:YES];

                                          }
                                          failure:^(AFHTTPRequestOperation *operation, NSError *error) {

                                              [self.navigationController popToRootViewControllerAnimated:YES];

                                          }];

}

回答1:

An easy way to accomplish what you want to do is to build some simple logic into your main root view controllers -(void)viewWillAppear method and use a delegate callback to flip the logic switch. basically a "back reference" to the root controller. here is a quick example.

main root controller (consider this controller a) - well call it controllerA set a property to keep track of the jump status

@property (nonatomic) BOOL jumpNeeded;

setup some logic in

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.jumpNeeded ? NSLog(@"jump needed") : NSLog(@"no jump needed");

    if (self.jumpNeeded) {
        NSLog(@"jumping");
        self.jumpNeeded = NO;
        [self performSegueWithIdentifier:@"controllerC" sender:self];
    }   
}

Now, in your main root controller,when a tableview row is selected do something like this when pushing to controllerB in your tableView did select method

[self performSegueWithIdentifer@"controllerB" sender:self];

then implement your prepare for segue method

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

  //setup controller B
  if([segue.identifier isEqualTo:@"controllerB"]){
    ControllerB *b = segue.destinationViewController;
    b.delegate = self;  //note this is the back reference
  }

  //implement controller c here if needed
}

Now move on to controllerB you need to set a property called "delegate" to hold the back reference and you need to import the header file from the root controller

#import "controllerA"

@property (nonatomic,weak) controllerA *delegate;

then just before you pop back to controllerA, you set the flag

   self.delegate.jumpNeeded = YES;
    [self.navigationController popViewControllerAnimated:YES];

and that is about it. You don't have to do anything with controllerC. There are a few other ways to do, but this is pretty straight forward for your needs. hope it works out for you.



回答2:

If I understand you correctly, you have a stack of view controllers:

A (root) - B - C - D - E

And you want it to become:

A (root) - F

Right? In that case:

NSArray *viewControllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray array];

// preserve the root view controller
[newViewControllers addObject:[viewControllers objectAtIndex:0]];
// add the new view controller
[newViewControllers addObject:viewThreadController];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];

Swift 4

// get current view controllers in stack and replace them
let viewControllers = self.navigationController!.viewControllers
let newViewControllers = NSMutableArray()

// preserve the root view controller
newViewControllers.add(viewControllers[0])
// add the new view controller
newViewControllers.add(viewThreadController)
// animatedly change the navigation stack
self.navigationController?.setViewControllers(newViewControllers as! [UIViewController], animated: true)


回答3:

I think
[self.navigationController pushViewController:viewThreadController animated:YES]; is using a different NavigationController than the statement before that. Because after popping to the root view Controller you loose the navigation Controller you are in. Solve that using this code instead

UINavigationController *nav = self.navigationController; 
[self.navigationController popToRootViewControllerAnimated:NO];
[nav pushViewController:viewThreadController animated:YES];

I also think that this wont solve your whole problem. You will probably get an error saying that two fast popping and pushing may invalidate the NavigationController.
And to solve that you can either push the NavigationController in the viewDidDissappear Method of the 2nd View Controller or push it in the viewDidAppear Method in the Main View Controller(item listing).



回答4:

Sharing a category on UINavigationController based on Dave DeLong's answer that we use in our application to keep the back button always working as required.

@implementation UINavigationController (PushNewAndClearNavigationStackToParent)

- (void) pushNewControllerAndClearStackToParent:(UIViewController*)newCont animated:(BOOL)animated
{
    NSArray *viewControllers = self.viewControllers;
    NSMutableArray *newViewControllers = [NSMutableArray array];
    
    // preserve the root view controller
    [newViewControllers addObject:[viewControllers firstObject]];
    // add the new view controller
    [newViewControllers addObject:newCont];
    // animatedly change the navigation stack
    [self setViewControllers:newViewControllers animated:animated];
}

@end


回答5:

NSArray *viewControllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray array];

// preserve the root view controller
for(int i = 0; i < [viewControllers count];i++){
    if(i != [viewControllers count]-1)
        [newViewControllers addObject:[viewControllers objectAtIndex:i]];
}
// add the new view controller
[newViewControllers addObject:conversationViewController];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];


回答6:

I don't think this is possible because popping back will deallocate everything.

I think a better way is to put your data on a model singleton class, pop to the rootviewcontroller and listen for the pop to end. Then you check if there is some data stored on the model so that you know if you should push a new viewcontroller.



回答7:

I used Dave's answer as inspiration and made this for Swift:

let newViewControllers = NSMutableArray()
newViewControllers.addObject(HomeViewController())
newViewControllers.addObject(GroupViewController())
let swiftArray = NSArray(array:newViewControllers) as! Array<UIViewController>
self.navigationController?.setViewControllers(swiftArray, animated: true)
  1. Replace HomeViewController() with whatever you want your bottom view controller to be
  2. Replace GroupViewController() with whatever you want your top view controller to be


回答8:

If what you want to do is navigate in any way inside the same navigation controller... you can access the navigationStack A.K.A. viewControllers and then set the viewcontrollers in the order that you want, or add or remove some... This is the cleanest and native way to do it.

        UINavigationController* theNav = viewControllerToDismiss.navigationController;
        UIViewController* theNewController = [UIViewController new];
        UIViewController* rootView = theNav.viewControllers[0];
        [theNav setViewControllers:@[
                                     rootView,
                                     theNewController
                                     ] animated:YES];

do any necessary validations so you don't get any exception.