Skip to content

Instantly share code, notes, and snippets.

@nicklockwood
Last active February 14, 2017 09:31
Show Gist options
  • Save nicklockwood/7447381 to your computer and use it in GitHub Desktop.
Save nicklockwood/7447381 to your computer and use it in GitHub Desktop.
Why I still prefer nibs to storyboards.

Storyboard Segues initially seem like a pretty cool way to construct interfaces using minimal glue code. But actually, ordinary nibs already support this, and 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.

- (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 (or 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];
    }
}

UPDATE:

In the interests of fairness, you could use a similar trick to make using prepareForSegue no less elegant than individual actions. If your base class overrides -prepareForSegue:sender: as follows, then you can split your preparation logic up into individual methods in exactly the same way as when using nibs:

@implementation BaseViewController : UIViewController
{
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        SEL selector = NSSelectorFromString([segue.identifier stringByAppendingString:@":"]);

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

        //note: in practice it might be more useful to pass the segue
        //or segue.destination rather than sender, but that's up to you
        [self performSelector:selector withObject:sender];
            
#pragma clang diagnostic pop

    }
}

My point remains however that Storyboards offer no compelling advantage over nibs from the point of view of rapid prorotyping or reduction of boilerplate code.

@Inferis
Copy link

Inferis commented Nov 13, 2013

That update is interesting. I dislike Storyboards mainly for the mess that prepareForSegue:sender: is, but that dynamic approach makes it more interesting. Although I can see nonseasoned developers freaking out by the automagic action- at-a-distance. ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment