简体   繁体   中英

Custom segue to push a specific UIViewController to UINavigationController

I want to create a custom segue that acts in the same way as the standard push segue does when used on UINavigationController view controllers. I've implemented my custom segue:

CustomSegue.m

    -(void)perform {
    UIViewController *source = (UIViewController *)[self sourceViewController];
    UIViewController *dest =(UIViewController *)[self destinationViewController];
    if (1==2 //test) {
        [source.navigationController pushViewController:dest animated:YES];
    }
    else {
        UIViewController *altDest = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL]
         instantiateViewControllerWithIdentifier:@"alternateView"];
        [source.navigationController pushViewController:altDest animated:YES];
    }

As you can see, the reason I want to use a custom push segue is so that I can decide which view controller to push based on the user configuration (currently only checking a trivial 1==2 expression). I can instantiate the alternate view controller with no issue, but what I want to be able to do is go back and forth without reloading the view controller each time (using the back and next buttons). Is there a way to retrieve an existing instance from the storyboard, or some standard way of doing this?

Instead of a custom segue with its perform , the way to do what you describe, ie choose in real time whether to push dest or altDest , is either (1) do not use segues at all and just call pushViewController directly as you are doing here, or (2) prepare two segues emanating from the view controller as a whole, and call performSegueWithIdentifier: to say which we should perform.

As for going directly from dest to altDest , you can push altDest on top of dest and then remove dest from the stack of the navigation controller's view controllers.

Like so much about about iOS, this is all so much easier and more obvious if you do not use a storyboard at all. This is why I don't like storyboards: they are so simple-minded and limiting, and they distract one's attention from the way iOS really works.

There is no way to retrieve an existing controller from a storyboard -- it would be nice if there were a controllerWithIdentifier: method to do that, but there isn't. Segues (other than unwinds) always instantiate new controllers, so I don't think you can do what you want with a segue. If you want to be going forward (push) to the same controller multiple times, then you need to do it in code by creating a property that points to your controller, and checking if that controller exists before pushing to it.

As the others have pointed out, you can't use a segue to push to an existing instance of a controller. The process of performing a segue always creates a new instance the destination controller for you.

Personally, when I'm jumping between existing instances of view controllers, I think "container view controller", such as a UIPageViewController , which makes it really easy to transition between two or more controllers, without necessarily reinstantiating them every time.

If you don't like the constraints the page view controller imposes (eg maybe you don't like the fact that iOS 5 version only supports page curl transitions, or that iOS 6 only adds the scroll transition, and you want something else), then you'd do a custom container view controller.

For example, if I wanted to jump between two view controllers and not reinstantiate them every time, I'd first create a custom container view controller, the "parent", and make sure I have a property to keep track of which child I'm currently at:

@property (nonatomic) NSInteger childViewIndex;

If supporting iOS 6.0 and above only, I'd then add a "container view" to my parent view controller's scene. If supporting iOS versions prior to 6.0, I'd add a standard UIView to the scene and then manually instantiate the first child controller:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    UIViewController *controller;

    // add the first child

    UIViewController *controller = [self addChildWithIdentifier:@"One"];
    [self.containerView addSubview:controller.view];
    [controller didMoveToParentViewController:self];
    self.childViewIndex = 0;
}

- (UIViewController *)addChildWithIdentifier:(NSString *)storyboardIdentifier
{
    UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier];
    [self addChildViewController:controller];
    controller.view.frame = self.containerView.bounds;
    controller.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    return controller;
}

Then, when I want to transition to the second child (or transition back to the first child), I'd call the following routine in the parent view controller:

- (void)transitionToViewControllerIndex:(NSInteger)index
{
    // don't do anything if we're trying to transition to ourselves!

    if (index == self.childViewIndex)
        return;

    // identify the two controllers in question

    UIViewController *sourceController = self.childViewControllers[self.childViewIndex];
    UIViewController *destinationController;

    // if we're asking for page 2, but we only have one defined, then we'll have to instantiate it

    BOOL instantiateDestination = (index == 1 && [self.childViewControllers count] < 2);

    if (instantiateDestination)
        destinationController = [self addChildWithIdentifier:@"Two"];
    else
        destinationController = self.childViewControllers[index];

    // configure the destination controller's frame

    destinationController.view.frame = sourceController.view.frame;

    // if you're jumping back and forth, set the animation appropriate for the
    // direction we're going

    UIViewAnimationOptions options;

    if (index > self.childViewIndex)
    {
        options = UIViewAnimationOptionTransitionFlipFromRight;
    }
    else
    {
        options = UIViewAnimationOptionTransitionFlipFromLeft;
    }

    // now transition to that destination controller

    [self transitionFromViewController:sourceController
                      toViewController:destinationController
                              duration:0.5
                               options:options
                            animations:^{
                                // for simple flip, you don't need anything here,
                                // but docs say this can't be NULL; if you wanted
                                // to do some other, custom annotation, you'd do it here
                            }
                            completion:^(BOOL finished) {
                                if (instantiateDestination)
                                    [destinationController didMoveToParentViewController:self];
                            }];

    self.childViewIndex = index;
}

Thus, to transition to the second child view controller, you could simply call:

[self transitionToViewControllerIndex:1];

If you want to transition back, you could call:

[self transitionToViewControllerIndex:0];

I'm only scratching the surface here, but container view controllers (or if none of the standard ones do the job for you, a custom container view controller) is precisely what you need.

For more information, see:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM