Easier Multiple Storyboards in iOS with Custom Segues

storyboard-example

One of our latest iOS projects has quite a few view controllers. A few weeks ago, John Fisher wrote about the pain of large storyboards and our solution of using multiple storyboards, but we’ve found an even better solution. This technique is more reliable, easier to use and has less code. Win, win, win!

Custom Segues to the Rescue

John’s post described RBStoryboardLink, which uses a clever system to dynamically re-parent a view controller. We ran into problems with this because it didn’t perfectly handle auto layout or dynamic navigation bar visibility. A better solution is to dynamically change the destination view controller before a segue starts. That allows iOS to do all the view layout calculation without any problems. We wrote a custom segue, AOLinkedStoryboardSegue, to do just that.

Xcode 5 supports custom segues quite nicely. They appear on the menu when you control-drag to create a segue, so your custom segues are as easy to use as one of the built-ins. Xcode even translates the Objective-C class name to something more readable. Our AOLinkedStoryboardSegue class appears as linked storyboard.

Lastly, we wanted an easy way to set the destination view controller. Segues don’t have any editable properties, so we repurposed the identifier to be the name of the view controller you want to link to using the syntax controller@storyboard. If you omit the controller name, it will link to the initial view controller.

Here’s a short video showing how to push from one storyboard to a view controller in another storyboard. The steps are:

  1. Drag a placeholder view controller onto the storyboard.
  2. Control-drag to create a segue to the placeholder.
  3. Choose “linked storyboard” on the segue menu.
  4. Select the newly created segue.
  5. Edit the segue identifier to be the linked view controller, in this case it’s the initial view controller in destination.storyboard, so enter @destination.

Show Us the Code Already!

The header file AOLinkedStoryboardSegue.h doesn’t really need to publish the helper method for loading a view controller, but we found it to be useful in a couple other places.

#import 

@interface AOLinkedStoryboardSegue : UIStoryboardSegue

+ (UIViewController *)sceneNamed:(NSString *)identifier;

@end

The class file AOLinkedStoryboardSegue.m just swaps out the placeholder view controller with the real destination. We only use push transitions between storyboards, but you can sub-class this or change the segue identifier syntax to also specify the transition style.

#import "AOLinkedStoryboardSegue.h"

@implementation AOLinkedStoryboardSegue

+ (UIViewController *)sceneNamed:(NSString *)identifier
{
    NSArray *info = [identifier componentsSeparatedByString:@"@"];

    NSString *storyboard_name = info[1];
    NSString *scene_name = info[0];

    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboard_name
                                                         bundle:nil];
    UIViewController *scene = nil;
    
    if (scene_name.length == 0) {
        scene = [storyboard instantiateInitialViewController];
    }
    else {
        scene = [storyboard instantiateViewControllerWithIdentifier:scene_name];
    }
    
    return scene;
}

- (id)initWithIdentifier:(NSString *)identifier
                  source:(UIViewController *)source
             destination:(UIViewController *)destination
{
    return [super initWithIdentifier:identifier
                              source:source
                         destination:[AOLinkedStoryboardSegue sceneNamed:identifier]];
}

- (void)perform
{
    UIViewController *source = (UIViewController *)self.sourceViewController;
    [source.navigationController pushViewController:self.destinationViewController
                                           animated:YES];
}

@end

 

Conversation
  • Jacques says:

    This is a nice elegant solution, its a real pity apple doesnt support this type of behaviour by default when apple want you to use multiple storyboards already

  • This solution is the best solution ever, I use it to split my storyboard without any problems and limitations!

    By the way, the perform code should check if you push navigation controller to navigation controller.

    – (void)perform
    {
    UIViewController *source = (UIViewController *)self.sourceViewController;

    if ([self.destinationViewController isKindOfClass:[UINavigationController class]]) {
    [source.navigationController presentViewController:self.destinationViewController animated:YES completion:nil];
    } else {
    [source.navigationController pushViewController:self.destinationViewController
    animated:YES];
    }
    }

  • Ken says:

    I stubbled upon this and looks great, however I can’t seem to make it work. When I try to tap the button it just blinks. I did a breakpoint to see if it was running the code and it was. Just never seems to change the view? Any ideas?

    • Ken Fox Ken Fox says:

      You could switch the segue class to a simple push to verify that you’ve wired up everything properly. It’s easy to accidentally miss something in the storyboard editor. If the wiring is correct you’ll segue to the blank placeholder screen. Typos in the destination storyboard name should be easy to catch because UIStoryboard will throw an exception if it can’t find a storyboard with the given name.

      Only other thing I can think of is the navigation controller is missing or wired in wrong. I’m not sure if Xcode will let you add segues to things without a navigation controller, but if it does I bet it wouldn’t work.

  • Jonathan says:

    Hi! This looks amazing. I am using Xcode 6 beta 4, and it keeps crashing… is there something I should take into account?

    I created new class files in objective c, replaced the code with your code, crafted a segue by control dragging.. set the class of the segue to the name of your segue, set the name of the segue to @Clearings (the destination storyboard is Clearings.storyboard).. and it crashes as soon as I hit the link.

    • Jonathan says:

      Should I switch back to Xcode 5 and try that?

    • Jonathan says:

      This is what the log is throwing out:

      self Class AOLinkedStoryboardSegue 0x0007b7e4
      identifier NSString * @”@Clearings” 0x14d9fb40
      NSObject NSObject
      isa Class __NSCFString 0x3b510908
      info NSArray * 0x7b7e4 0x0007b7e4
      NSObject NSObject
      isa Class 0x7b7f8 0x0007b7f8

      And this is the line of code its highlighting on crash:

      NSArray *info = [identifier componentsSeparatedByString:@”@”];

      Suggestions?

      I tried making sure there was a navigation controller prior to the switch move… thats in place.

    • Jonathan says:

      Also, I tried dropping a Nav controller in before the Clearings Storyboard… and got this:

      ‘NSInvalidArgumentException’, reason: ‘Pushing a navigation controller is not supported

      I guess next I will try pushing to the second scene with that nav controller still there…

    • Jonathan says:

      Wow… this is fascinating… I have been working on this for several weeks now… this ONE thing… ha!

      Heres what happened… I pushed to the initial VC instead of the Nav controller I dropped into the storyboard I was transitioning into… crashed but says this:

      014-08-20 11:15:08.850 IntegralCoach1.2[572:60b] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’,

      reason: ‘Storyboard () doesn’t contain a view controller with identifier ‘Clearings1”

      And Im sitting here wondering WHY the name of the storyboard its looking for is “0x175aeb90” !!

      No wonder it doesn’t have a VC with that name, it doesn’t exist! Gonna try to suss this out… somethings off with the naming process I think… u agree?

      • Ken Fox Ken Fox says:

        I mentioned earlier to just edit the type of the segue to a standard push and see if you land on the blank placeholder. If that works, switch back to the custom segue and set a break point on the storyboard lookup, i.e. line with “if (scene_name.length == 0) {“.

        It should be easy to spot a typo when you are at the breakpoint.

        The main thing I like about this custom segue approach is that there’s so little code it’s easy to debug. We’ve also added quite a few extra options in our code such as new transition animations. Good luck!

        • Jonathan says:

          Wow!! A response! Thank you, thank you, thank you…

          I wonder if I have tinkered too much with it to warrant going forward without restoring the last snapshot… from right after I dropped in your files…

          Ill start with what you have suggested… but I should probably mention now that I need help understanding what it means to set a break point in the storyboard lookup…

          Ill start with what you have suggested

          • Jonathan says:

            Ah… a normal transition is crashing it!! Lemme look at this for a sec… hmm..

          • Jonathan says:

            Ok… trying this… should the destination storyboard have a nav controller? and should I be concerned about the “can’t push to nav controller” message I get back?

          • Jonathan says:

            So… I got the transition set up so that I have confirmed that it works… the way I tried to swap it crashed (popover)… but I put it back to ‘show’ and it works… so now I am gonna try to get the breakpoint in there…

        • Jonathan says:

          Ok!! It looks like you already have that included in your code…

          if (scene_name.length == 0) {
          scene = [storyboard instantiateInitialViewController];
          }
          else {
          scene = [storyboard instantiateViewControllerWithIdentifier:scene_name];
          }

          return scene;

          re “It should be easy to spot a typo when you are at the breakpoint.”

          How do I find the ‘typo’ ?

          I copied your code DIRECTLY into new class files… was that a mistake? Is there something that needs to be adapted to my particular project??

        • Jonathan says:

          Looking for typos… this stands out to me… does this look wrong?

          identifier NSString * @”@Clearings” 0x17d78280

    • Jonathan says:

      I found the storyboard ID blank in the designation storyboard… under identity… so perhaps this is the issue? … tried giving it a storyboard ID (had figured the file name was the identifier prior to this)…

      Now this:

      Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Pushing the same view controller instance more than once is not supported ()’

    • Jonathan says:

      Last note here for the moment… tried removing the destination storyboard nav controller… so I can send to the root (@Clearings) and see if its finding the right storyboard in the first place… I got this:

      ‘NSInternalInconsistencyException’, reason: ‘Invalid parameter not satisfying: destination’

      Which I think means it can’t find a storyboard with that name… or a root storyboard with the identifier Clearings.Storyboard…

      Now… I just noticed that After deleting the nav controller it did not set the initial VC as such… so, I set it as the initial VC… and… it crashes without logging anything…

      On the left side of the console log it prints out a lot, including this:

      self AOLinkedStoryboardSegue *const 0x1759e3f0 0x1759e3f0
      source UIViewController * 0x176b9920 0x176b9920

      Obviously I am WAY new at this… feel free to be frank… should I not be trying this with 6 beta 4, should I try this with 5?

      • Brian W says:

        this is old, but look at my most recent post if you want to see potential solutions to this issue, I saw it as well.

  • David says:

    Hi,
    Thank for this tip, it look great.
    dummy question, where can I find the sample code ?
    Thanks a lot.

  • Jiang Guogang says:

    How to handle the tab controller with this method?

  • Amin says:

    I had the same question. How to handle UITabBarController with your method. I tried but I failed to get it to work.

  • drew.. says:

    awesome tip.. thank you. I groovy extension of this would be to have a special droppable item on the storyboard that doesn’t require the size of the placeholder ViewController.. something like a minor circle akin to a flowchart jump..

  • Osaka says:

    Thanks for this! So Awesome !!

  • Brian W says:

    Hey folks, I just got this all setup and wanted to share a couple snags that might apple:
    1) Be sure to have an initial view controller designated in your ‘destination’ storyboard (click a view controller, then go to the attribute inspector on the right side, and be sure “Is Initial View Controller” is checked).

    2) Be sure to use the “@” symbol in the destination name

    3) My app was not using a nav controller inside the first storyboard because reasons. So I had to change a few lines in AOLinkedStoryboardSegue.m to this:

    – (void)perform
    {
    UIViewController *source = (UIViewController *)self.sourceViewController;
    [source.navigationController pushViewController:self.destinationViewController
    animated:YES];
    [source presentViewController:self.destinationViewController animated:YES completion:nil];
    }

    Thanks so much for this tutorial, I’ve skinned this cat a few times and this is the best approach. Its absolutely insane that there is not more support from XCode for this totally normal need for any sized team of developers.

    • Santosh says:

      2015-04-09 17:32:26.083 Authenticator[599:60b] *** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key debugLabel.’
      *** First throw call stack:
      (0x2edb4f83 0x3962fccf 0x2edb4c99 0x2f6f9f23 0x2f70bb55 0x2ed250eb 0x31940ebb 0x3189960b 0x316f5bcd 0x315d62ed 0x315d6269 0x3176236b 0x3167fd63 0x3167fb6d 0x3167fb05 0x315d1d59 0x3124f62b 0x3124ae3b 0x3124accd 0x3124a6df 0x3124a4ef 0x3124421d 0x2ed80255 0x2ed7dbf9 0x2ed7df3b 0x2ece8ebf 0x2ece8ca3 0x33c42663 0x3163514d 0xba651 0x39b3cab7)
      libc++abi.dylib: terminating with uncaught exception of type NSException

      downloaded project from…. https://github.com/RisingOak/otp-authenticator

      the next viewcontroller not showing ..errror…

  • aone says:

    This works awesome. Incorporated Brian W’s suggestions, added T. Chou’s check for UINavigationController type. Thank you very much!

  • Akshay says:

    Any solution for the same in swift!!!!!

    • Rusty says:

      Here you go. Hope it’s useful
      https://github.com/qblu/LinkedStoryboardSegue

      It’s not terribly different from what’s coming with iOS 9 so transition should be pretty seamless

      • Ken Fox Ken Fox says:

        Thanks for converting the code to swift. I think you switched the target naming convention accidentally? It reads better to me to link to scene@storyboard.

  • Bill Johnson says:

    Works like a charm.

    Now I’m trying to create a ‘back’ button, but:

    [self dismissViewControllerAnimated:YES completion:nil];

    doesn’t work. Any reason why it shouldn’t?

    Bill

  • Vijay says:

    Ken, this is amazing!! Thanks for posting.

  • Khoa Pham says:

    Have you tried the `awakeAfterUsingCoder` approach ?

  • Comments are closed.