UIStoryboard Issues

Background

In August I joined a new start-up as lead (and only) iOS Developer. I managed to convince the founders that targeting iOS 5 was a good idea and as such I’ve embraced a lot of new iOS 5 only technologies in the app including UIStoryboard.

After a few months of development with UIStoryboard I’ve decided that the technology just isn’t mature enough to be used in a complex iOS application and have now replaced all* UIStoryboard with traditional NIBs.

(* I have left in standalone storyboards for parts of the app heavily in flux, but will be replacing this usage with NIBs in the future).

Storyboard Editor

The storyboard editor in Xcode 4 combines all the problems of the Interface editor (e.g. broken keyboard navigation, somewhat broken selection semantics, amnesia for useful settings like “show bounds”, etc) with the inability to edit interfaces on a small (17″) screen. Once the storyboard expands beyond a half dozen or so view controllers the storyboard editor becomes very hard to use, with constant panning and zooming to find the view controller you want to edit.

One problem with storyboards with lots of view controllers (my app had around 30 view controllers in a single storyboard) is that everything looks the same. Visually a lot of view controllers end up being some kind of variation on a white rectangle (esp. when zoomed out). I found myself tending to rely on a knowledge of the storyboard “geography” to find the view controller I was looking for. And even familiar with the layout of the view controllers I still found myself hunting and pecking for the right view controller (“oh no wait, that’s the parent UINavigationController, I must want the child instead”).

A somewhat related but inverse problem is that it seems to be impossible to use UITableView static cells & prototype cells outside of a storyboard. This seems strange. Being forced to use storyboards if you want to use this very useful feature is serious problem. I have to assume this is an oversight.

“Spaghetti” Segues

With moderately complex storyboards the view controllers and their segues begin to resemble spaghetti. I found this especially common when I had multiple “detail” view controllers that could be accessible from each other (think of a twitter app – you can view people in a people table and tweets in a tweet table, and drill down in people/tweet detail screens. But you can also access a person detail screen from an individual tweet detail screen and vice versa). I wonder if the master view controller source list (presented on the left of storyboard editor) should not be the primary view for the editor, with the ability to focus on one view controller at a time?

Segue Identifiers

It becomes readily apparent that the segue identifier field (settable in the editor) is the best way to differentiate between segues in large prepareForSegue: implementations (see more on this later). It would be very handy if Xcode 4 supported a “warn on missing segue identifier” option when storyboards are “compiled” into individual NIBs. (feature request)

View Controller Identifiers

It is possible to assign view controllers in a storyboard an identifier. Unfortunately this identifier property is not exposed in the UIViewController class. This makes it very hard to perform safe introspection of the view controller hierarchy at runtime. It would be really nice if identifier was exposed for view controllers as well as for segues. (Feature request)

Push-only Segues

Segues are by default “additive” only: you can push a view controller onto the navigation stack or present it modally but it isn’t possible to perform other common navigation tasks without resorting to custom segues. For instance you can’t jump to a view controller further down the navigation stack or change the tab of a tab bar. And the ability to jump to an arbitrary view controller in the view controller hierarchy (without a push) doesn’t seem possible at all.

Custom segues do allow you to do some of this. In fact I have written a custom segue to jump to a different tab in UITabBarController, but it was rather hacky code to get around the fact that the segue automatically created a new destination view controller for me.

I think it would be handy if these, more complex segues, were built into the UIStoryboard system by default.

Misuse sender field.

A common iOS design pattern is to create a new detail view controller, populate it with the relevant detail and then present it (either push onto the navigation stack or present modally). Generally I’d accomplish this with a custom “initWithFoo:” method on the detail view controller.

CUser *theUser = [self.fetchedResultsController objectAtIndexPath:indexPath];
CUserProfileViewController *theViewController = [[CUserProfileViewController alloc] initWithUser:theUser];
UINavigationController *theNavigationController = [[UINavigationController alloc] initWithRootController:theViewController];
[self presentViewController:theNavigationController animated:YES completion:NULL];

With storyboarding there’s no clear way to pass data to a detail view controller. The best I could come up with was by abusing the sender parameter to pass the data the detail view controller needs, e.g.:

CUser *theUser = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self performSegueWithIdentifier:@"ID_USER_DETAIL" sender:theUser];

The performWithSegue: method would then take the data and hand it to the already created destination view controller:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
    if ([segue.identifier isEqualToString:@"ID_USER_DETAIL"])
        {
        UINavigationController *theNavigationController = AssertCast_(UINavigationController, segue.destinationViewController);
        CUserProfileViewController *theViewController = AssertCast_(CUserProfileViewController, theNavigationController.topViewController);
        theViewController.user = sender;
        }
    }

(Note I use my AssertCast_ macro to make sure my assumptions about the view hierarchy are correct, I’ve been bitten too many times in the past by bad view controller hierarchy assumptions to not be paranoid here)

Not only is this a lot of code and complexity to replace something that was rather simple before, it seems to be an abuse of the “sender” parameter. Sender should really be (at least according to tradition) the button or the table cell or the map annotation, etc that triggered the segue and not the data the destination view controller needs to configure itself.

I tried to build my own performSegueWithIdentifier:sender:userInfo: and prepareForSegue:sender:userInfo: methods in a UIViewController category but it was clear it was going to have to do some nasty things (like define a prepareForSegue:sender: in the category in question) and I decided it was a no-go.

Perhaps I’m missing something here and there is a better way to accomplish this.

Bloated prepareForSegue:

My root view controller has become a source view controller for lot of segues and therefore its prepareForSegue: has become a stupidly large method filled with a lot of “if (segue.identifier isEqualToString:@”…”)” statements in a row. I don’t like this. I much preferred the way the same code was distributed to many view controllers instead.

I find this problem causes the code to become more brittle and less flexible. If I change the view controller hierarchy I need to find the relevant block of code in the source view controller and move it to the new view controller. Previously I didn’t have to do this at all. This totally eliminates the flexibility that storyboards can provide.

No user defined relationships

This was really the nail in coffin for storyboards for me.

I needed to create a new container view controller type that could flip between multiple child view controllers (rather like a UITabBarController but with different UI and animation). I implemented the view controller with a child “viewControllers” property and got the transitions and UI working and then realised that I couldn’t wire it up in a UIStoryboard successfully. I quickly realised that only UINavigationController and UITabBarController could have non-segue relationships to other view controllers (*). This basically made it impossible to use one large storyboard and would need to use traditional techniques (static view controller instantiation in this case) and break my single monolithic storyboards up into smaller storyboards that I could load root view controllers from as needed. Unfortunately because of the spaghetti problem I discussed previously this was going to be ugly.

At this point, combined with the other problems and issues I’d encountered up to now I decided to rip out my storyboard and replace it with the traditional one (or more) NIB per view controller.

No code size benefits

I spent almost a day replacing my storyboard (and related code) with NIBs (I am sure I have introduced new bugs into the app in the process that will take further time to find and fix). In doing so I actually reduced the line count by about 400 lines of code. It’s pretty clear that storyboards wont reduce the total of number of lines code in the app and due to the problems with prepareForSegue: discussed previously I don’t think storyboards will allow you to iterate designs more quickly than using NIBs.

Is this my fault?

So I’m left wondering if this could my fault, that I’m missing something or just don’t understand how I should be using storyboards. I don’t think so. I do think that I could learn to stop worrying about the sender/userInfo issue and perhaps I could work out a smart way to solve the prepareForSegue: bloat problem. But really, I don’t have the time for this, I need to be able to implement UI in a speedy manner without having to solve the “how do I make this work with UIStoryboards” problem.

Summary

I do think the UIStoryboard technology has a lot of potential but as I’ve discussed there are some rather severe issues with the current implementation that make it burdensome to use. That said I look forward to further iterations of the technology. Also if anyone has worked around these problems or think I’m doing it wrong please let me know.

This entry was posted in Default. Bookmark the permalink.
  • http://twitter.com/bjhomer BJ Homer

    Attempting to cram the entire interface of an app into a single storyboard is often a bad idea. In the same way that we use classes to implement separation of concerns, you can use storyboards to separate various “modules” of your app. You might have a “Settings” storyboard, a storyboard for each tab, etc. There’s no need to shove everything into a single storyboard.

    The ability to use static cells and design prototype cells in tableviews is, in my mind, the killer feature for storyboards. I have a number of storyboards with only a single view controller in them, but I use a storyboard instead of a xib simply because I can design my tableview cells inline.

  • http://toxicsoftware.com schwa

    (Thanks for copying the comment from the gist – you raise a good point so am very glad you did)

    The trouble with breaking up your UI into multiple storyboards is that it buys you very little over multiple NIBs. And you still have all those problems discussed above. You can’t easily link between individual VCs spanning multiple storyboards so you’re back to writing a lot of code (again).I do plan on using Storyboards still – like you mention it’s the only way for the new UITableView features (I have to assume that’s a bug on Apple’s part). And I may keep storyboards for some simpler parts of the app (e.g. new user signup/login). But I definitely won’t try to make the entire app use storyboards again.

    I’ll happily re-visit the technology again once improvements have been made. I have faith it can be made a lot better.

  • Charles Choi

    FWIW, my first impression of working with UIStoryboard was inflexible control flow, particularly when interleaving visual and code implementations of that flow. If it’s of any consolation, the problem with efficiently describing control flow in visual languages has long been noted in CS and it seems that it still has yet to be solved here.

  • http://jdmuys.myopenid.com/ Jean-Denis Muys

    One other thing I dislike about storyboard is that it makes it more difficult to work with other developers on the same app with source control. With separate nibs, you can split work responsibility more easily with little need to resolve conflict at pull/push time. Storyboards are just too monolithic.

  • http://toxicsoftware.com schwa

    There’s that. I haven’t encountered it myself (for most of this project I was the sole developer). Was hoping it would merge cleanly though.

  • http://twitter.com/thomasalvarez Thomas Alvarez

    “My root view controller has become a source view controller for lot of segues and therefore its prepareForSegue: has become a stupidly large method”

    I understand your complaint about the size of the method if you have a lot of segues (outgoing) one view controller. Could you explain how if you have a “root” view controller that has a lot of outgoing segues to other view controllers, how you don’t have a lot of code in it with presentViewController calls doing the same thing when you’re not using Storyboards?

    Btw, writing “feature request” in a blog post isn’t going to get you anything. File bug reports on Radar (https://bugreport.apple.com).

  • http://toxicsoftware.com schwa

    In a NIB based app I tend to pass the data needed by a VC as the VC is created (see my “initWithFoo:” example in the post.) This sprinkles the set up code amongst the VCs that need the data.

    In a storyboard based app the code for instantiation tends to get centralised in the source view controller.

    On top of that it because the destination view controller often is not the view controller that needs the data (often there is a UINavigationController in the way) you need to get from the source view controller to the actual view controller that needs the data.

    Look at the code example I posted in the body. And that just shows a prepareForSegue: for a single (destination) view controller – imagine if the source view controller had more segues. 3 segues in a source view controller isn’t uncommon.

    And yes, thank you for pointing out how to file a bug with Apple. I was expecting Tim Cook to read my blog and make sure these features were implemented.

  • Maxim Zaks

    Quantity defines the poison! IMHO story board is for prototyping and small Apps. It is madness to do monolithic storyboard for over 20 view controllers.

  • http://toxicsoftware.com schwa

    Madness! Madness I say.

    There’s nothing intrinsically wrong with UIStoryboard. I think if Apple improves upon it , it could be really useful. I just don’t think the current version of it is ready.

  • kra

    Definitely agree with you. I’m getting super weird behaviour on viewdidload/unload with container VCs. Bsically, the initial VC is a container VC, we have the children in the storyboard, loaded with instantiateVCWithIdentifier on demand. When a child VC is “off screen” (ie after removeFromParentVC), a memory warning does drop the views and outlets, as expected. But when it comes back (ie addChildVC), it loads only whatever is wired to the main view, the NSobject outlets not directly under the VC’s main view are just not loaded. Not even init’d. And even worse, wiring views into those nsobjects just crashes the app. Works fine if i move the children to a regular nib (one per nib).

  • Peter

    I cannot be more agree with you. Storyboards suck.

  • http://twitter.com/drewmckinney Drew McKinney

    I’m happy you posted this. I’m struggling with Storyboards from a fundamentals standpoint. I’m used to being able to access objects like the TabBar as an outlet without interference, but with Storyboards I am forced to grab the rootViewController from the window and trust that it’s correct. Granted, this happens without storyboards, but it adds a new level of confusion by not allowing me to connect some things to code but not others. Granted, I’m doing this to perform a more advanced interface action, but it’s nothing out of question for an developer creating any kind of quality App.

    Storyboards seem suited for those new to cocoa, allowing them to focus on the ViewController actions and not have to bother with the particulars of App delegate up front. Also, I’ve used Storyboards with designers to visually place interface elements, allowing them partial exposure to the development process. But as you mentioned, these are simply too inflexible for any kind of advanced use. 

  • Matt

    Enjoyed this post a lot.

    The bloated prepareForSegue is a bad smell, in the Refactoring sense. And I would have expected that the solution would be the same as in Refactoring: multiple UIStoryboardSegue subclasses. Unfortunately the inventors of storyboards didn’t think of that, so prepareForSegue is sent to the source view controller and we end up with spaghetti.And string identifiers – could anything be less maintainable, more error-prone? And they didn’t even think to show the identifier in big letters in the canvas part of the storyboard editor?Plus, the whole prepareForSegue architecture is just wrong; instead of letting the destination view controller specify its own designated initializer, so that it must be given the right data in order to be instantiated and can then do with that data what it likes, the source view controller winds up having to know the inner mechanisms of the destination view controller and set its properties or call its methods. That is not correct OOP design. Storyboards feel like a sop to beginners who don’t understand view controllers, but I don’t think they actually make life better for them or for anyone else. I do understand view controllers and I’m very nimble with them; storyboards just get in my way.Nevertheless, the basic idea is intriguing, especially the insight that the whole repertoire of things that must happen as a view transition takes place can be clumped into an object (a segue).

  • Pingback: Hitting the wall with Storyboards | a Small Teapot

  • http://waynehartman.com/ Wayne Hartman

    Great post.  I’ve been using storyboards for over a month now and have run into the same issues.  However, I did discover some even more rotten ones:

    1) You can’t just use segue.destinationController if you are using a VC that is wrapped in a navigation controller.  You must dig into the controller hierarchy and find it.  For example:

           UIViewController* viewController = nil;

            if ([[segue destinationViewController] isKindOfClass:[UINavigationController class]]) {             UINavigationController* navController = [segue destinationViewController];             viewController = (UIViewController)[navController topViewController];         } else {             viewController = (UIViewController)segue.destinationViewController;         }

    This is very troublesome to me and just doesn’t smell right.

    2)  There is no automatic prepareForSegue for an accessory view action on a table view cell.  You have to implement this yourself.  So much code…

    3)  There is a reproducible flaw I discovered with an overrelease on a UISearchViewController if you create one as part of your storyboard and a low memory situation occurs.  So much for ARC.    If you’d like an example project on this, fell free to hit me up.

    4)  If you are doing a Universal app, you must keep the segues in sync. If you change a segue name in the iPhone version, you must make a corresponding change in the iPad version, else use branching logic.  Having to duplicate or keep these sorts of things in sync really rubs me the wrong way.  The problem is that I can’t think of a good alternative.

    5)  Popovers on iPad are the worst.         a)  You cannot programmatically set the anchorView nor the anchorRect without using a private API.                 i) This is important to be able to do when you may not create everything through IB.         b)  The expected order of operations in a popover differ from other segue transitions:                ii) The view is loaded first, then the prepareForSegue call is made.                ii)  In other segue transitions, prepareForSegue is called first, then the view is loaded.                iii)This is detrimental to the design of your ViewControllers because some UI modifications may have been expected to be made based on models you set in prepareForSegue, thus causing you to write more code to overcome the difference in behavior…

  • Pingback: UIStoryboard Power Drill [Jason Lust] (iDevBlogADay) | zdima.net