Skip to content

Instantly share code, notes, and snippets.

@wolffan
Forked from nicklockwood/gist:7447381
Created November 13, 2013 11:23
Show Gist options
  • Save wolffan/7447517 to your computer and use it in GitHub Desktop.
Save wolffan/7447517 to your computer and use it in GitHub Desktop.

Storyboard Segues initially seem like a pretty cool way to construct interfaces using minimal glue code. But actually, nibs already supported this concept in a much more flexible way.

Certainly, a Storyboard lets you bind a button action up to display a view controller with no code, but in practice you will usually want to pass some data to the new controller, depending on which button you used to get there, and this means implementing the -prepareForSegue:sender: method, which rapidly becomes a giant if/elseif statement of doom, negating most of the benefit of the codeless segue:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"modalSegue"])
    {
        ModalViewController *controller = (ModalViewController *)segue.destination;
        controller.someProperty = someValue;
    }
    else if ([segue.identifier isEqualToString:@"navigationSegue"])
    {
        PushViewController *controller = (PushViewController *)segue.destination;
        controller.someProperty = someValue;
    }
}

To make a button that displays a view controller without storyboards, you need to write an IBAction method, but as you will see below, if you are passing data this actually results in less code than the equivalent prepareForSegue method:

Firstly, it's important to understand that it is possible to define View Controllers in a nib, just as you can in a Storyboard. This means that there is no need to hard code a specific controller class inside your IBAction. Define outlets for your linked controllers, and then instantiate them in the nib. This gives you the flexibility to either define the views for all of your controllers in-place in one nib (storyboard style) or to specify that they should load their contents from a separate nib file. (The latter is usually preferable since, unlike Storyboards, view controllers inside nibs are not loaded lazily, but if you're "rapid prototyping" you can easily cram your whole interface into one nib if you want to).

Your code then becomes:

- (IBAction)modalButtonAction:(id)sender
{
    self.modalViewController.someProperty = someValue;
    [self presentViewController:self.modalViewController animated:YES completion:NULL];
}
 
- (IBAction)navigationButtonAction:(id)sender
{
    self.pushViewController.someProperty = someValue;
    [self.navigationController pushViewController:self.pushViewController animated:YES];
}

And if for some reason you prefer to have a single routing method like the prepareForSegue approach, you can do that too; just bind all your buttons to a single action and inspect the sender to branch (which has the benefit over prepareForSegue that it is strictly typed, and doesn't require string comparison):

- (IBAction)buttonAction:(id)sender
{
    if (sender == self.modalButton)
    {
        self.modalViewController.someProperty = someValue;
        [self presentViewController:self.modalViewController animated:YES completion:NULL];
    }
    else
    {
        self.pushViewController.someProperty = someValue;
        [self.navigationController pushViewController:self.pushViewController animated:YES];
    }
}

OK. But we can all agree that if you are not passing any specific data to a view controller, the StoryBoard segue zero-code approach is still better than the very best you can achieve with nibs, right?

Wrong. If you create a BaseViewController class with the following methods and use it as the base class for all your controllers (you can define these methods as a category on UIViewController if you prefer), you can directly bind your button actions between two controllers in your nib, segue-style and avoid writing any glue code at all:

@implementation BaseViewController : UIViewController
{
    - (UIViewController *)controllerForSender:(id)sender
    {
        id responder = sender;
        while ((responder = [responder nextResponder]))
        {
            if ([responder isKindOfClass:[UIViewController class]])
            {
                return responder;
            }
        }
        return nil;
    }

    - (IBAction)push:(id)sender
    {
        UIViewController *parent = [self controllerForSender:sender];
        [parent.navigationController pushViewController:self animated:YES];
    }

    - (IBAction)presentModally:(id)sender
    {
        UIViewController *parent = [self controllerForSender:sender];
        [parent presentViewController:self animated:YES completion:NULL];
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment