Storyboard Flow Example

10 Practical Tips for iOS Developers Using Storyboards

Instead of a full-blown tutorial on how to create something with Storyboards, I’m going to cover highlight 10 things, in no particular order, that should help get you going.

Storyboards are one of the areas I spend most of my time in. I’m a big fan of being able to visualize the app that I’m working on. By simply dragging items onto a canvas, update position information, and set a few characteristics, you’ve created a user interface without touching underlying code. This is important because user interface code can quickly turn your code into a mess.

When I join a new project one of the first things that I’ll do is find their storyboards. They can be one of the more useful places to visit to get a very high-level view of the structure of the app.

Without using a visual editor you’re dealing with a very manual process to discover where things are flowing. You have to waste a lot of cycles just to get a mental picture for how a given view is laid out by navigating your way through source code. You could clearly reference design documents or run the app and navigate to the desired area, but I’d prefer to avoid that if I can. Finally, in certain situations, tweaking interface components becomes a tedious process. Instead of making quick adjustments within a storyboard you’re constantly building and running your app to verify things are where they need to be.

Storyboard Coe to Interface

Look how much code it takes just to create the simple interface to the right, and I haven’t even included any auto layout code to help us with positioning. I know there will be die-hard code folks out there, but I just can’t get past bloating my codebase–it’s nasty. Don’t get me wrong. For beginners there is value in understanding how to create a user interface object within code. It also helps to scan over the documentation for a given user interface object so you have a general awareness of what it can do.

The List

There can only be one… wait, nevermind

You don’t have to build your entire app in one storyboard. Split them up into several boards. Imagine having onboarding, settings, and a main board. This will save you headaches down the road when you app really magnifies in size. Interacting with boards in a team environment will be easier and finding the area you need to work within will be quicker.

What’s an exit segue and how do you use it

First, let’s define what a segue is. Say you have 2 scenes in your storyboard, with the first scene having a button. When you right-click from the button in Scene 1 and drag it to Scene 2, you’re creating a segue.

Storyboard Segue

Let’s say we select present modally. A modal state means that it is the #1 focus for the user. There is no easy way to simply go back to the first scene like you would see if you pushed a scene on a navigation stack. We could create a delegate to inform the first controller that we’re done, but that’s a little tedious. We could fire off a notification telling the first controller that we’re done, but that’s a little overkill. This is a great opportunity to use an exit segue. An exit segue works just like a segue, but returns to where you implement the UIStoryboardSegue action function. This is key! As long the exit segue can navigate backwards through the segues you’ve created, it will find the exit segue no matter where it is along that segue path.

Observe the complex view structure below.

Storyboard Exit Flow

If you simply right-click from a button to the exit segue indicator, you won’t see anything. It needs to detect a method you’ve created a UIStoryboardSegue action at the destination controller (where the green arrow is pointing). For example:

@IBAction func unwindToSceneA(unwindSegue: UIStoryboardSegue) {
    // be sure to give your unwind segue an identifier so you know where we're coming from     
}

Jumping Storyboards

You don’t need to draw a fancy segue in your storyboard to go where you want to go. You just need to initialize the storyboard and fetch the controller that you want to display. Once you have the controller told display then you can call the necessary display method.

var storyboard: UIStoryboard = UIStoryboard(name: "Settings", bundle: nil)
var modal: UIViewController = storyboard.instantiateViewControllerWithIdentifier("settingsStoryboardId") as UIViewController
self.presentViewController(modal, animated: true, completion: nil)
 
/* If you're fetching a controller in the same storyboard you're already on, 
 * then you can skip initializing a new UIStoryboard object.
 */
var modal: UIViewController = self.storyboard?.instantiateViewControllerWithIdentifier("customStoryboardId") as UIViewController
self.presentViewController(modal, animated: true, completion: nil)

Preview Editor

It can be quite tedious building and running your app all of the time to observe if your user interface tweaks had the impact you wanted. This is especially true when dealing with auto layout.

Storyboard Preview

Now that you have the preview editor open you can modify your view and watch how it reacts to your changes. You can also preview your interface on multiple screen sizes by pressing the + button on the lower-left side of the assistant editor panel.

Give Those Fingers a Break

If you have a button that you need to connect to your source code you can right-click + drag a line to your source file to generate an outlet for you.

Storyboard Create Outlet

On the flip side, you can generate an action for a given event by clicking + dragging into your source.

Storyboard Create Action

The final result from the above actions.

Storyboard Click Done

Now why in the world would you do this stuff? Well, the action is the most obvious. If you don’t create that @IBAction function then nothing will happen when you press the button. You could imagine adding code that’ll change the image that we’ve set in the UIImageView. In order to change the image we’ll need an @IBOutlet so we can access it.

Avoid Incredibly Complex Controllers

While your controllers can manage a lot of subviews, you risk making things a complete wreck by adding view on top of view on top of view. Pretty soon you’ll have a really hard time taking advantage of the entire purpose of using a visual editor–providing a clear view of what you’ve built. If you have a complicated view structure on a controller then it might be time to consider a couple of options.

You can use a xib (pronounced ‘zib’) or you can add a container view object to your scene & hide it until it’s needed. Usually I fall back to using a xib, but there are certain situations where the container view object works.

When you add a xib you’ll also add an accompanying source file that you’ll use to initialize the xib. For example, lets say we created ExampleView.xib and created a beautiful view. In order to load that view we’ll need to create ExampleView.swift and initialize that xib.

import UIKit
 
class ExampleView: UIView {
    
    // normal initialization
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.addExampleViewSubview()
    }
    
    // will be loaded if we use this class in a storyboard, for example
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.addExampleViewSubview()
    }
 
    func addExampleViewSubview() {
        var xib = NSBundle.mainBundle().loadNibNamed("ExampleView", owner: self, options: nil)
        var view: UIView = xib.first as UIView
        view.frame = self.frame
        self.addSubview(view)
    }
}

Placeholder Constraints

This is one for those who love a mixture of code + storyboards to manipulate constraints. While I do what I can to avoid writing constraints in code, it can be valuable in the event that you are using a view that is not part of a storyboard. It can be a downright terrible experience when you’re trying to create constraints in code and you’re trying to work on your user interface in the storyboard. Never fear, you can easily tell Xcode that a particular constraint is a placeholder. This means it’ll be ignored when you build and run your app.

Storyboard Placeholder

Default View Controller

You may need to change which scene is loaded with the storyboard. In previous versions of Xcode you would select a scene and toggle Is Initial View Controller. This has changed in recent versions of Xcode. Now you’ll need to search your object library for Storyboard Entry Point. You can then drag and drop it on your desired scene. You can only have one of these active at a time so you can can drag and drop it on a different controller whenever you’d like.

Why would you worry about changing the entry point? Personally, I use it when I’m testing different controllers and I don’t want to go through the motions of tapping a ton of buttons in order to get to the controller. If you just update the storyboard entry point then it’ll load up immediately.

Custom Segue Transitions

If you select a segue you may have noticed that it comes with a few pre-loaded transitions; cover vertical, flip horizontal, cross dissolve, partial curl. What if you want something a little more custom? In that case you’re going to need to create a custom UIStoryboardSegue.

For a simple example, create a new Swift file called CustomSegue. The code below will be executed when our segue is performed (upon button tap).

import UIKit
 
class CustomSegue: UIStoryboardSegue {
    
    var startingPoint: CGPoint = CGPoint(x: 0, y: 0)
    
    override func perform() {
        var source = self.sourceViewController as UIViewController
        var destination = self.destinationViewController as UIViewController
        
        // Add the destination view as a subview (temporarily)
        source.view?.addSubview(destination.view)
        
        // Set the start scale
        destination.view.transform = CGAffineTransformMakeScale(0.05, 0.05)
        
        // Original center point
        var originalCenter = destination.view.center
        destination.view.center = self.startingPoint
        
        UIView.animateWithDuration(0.225, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
            destination.view.transform = CGAffineTransformMakeScale(1.0, 1.0)
            destination.view.center = originalCenter
        }) { (finished) -> Void in
            destination.view.removeFromSuperview()
            source.presentViewController(destination, animated:false, completion:nil)
        }
    }
}

This will expand the destination view from a given starting position that we set when the button is pressed. In this case I set the starting position to the center position of the button on the first scene (the source scene).

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let custom = segue as CustomSegue
    custom.startingPoint = self.nextImage.center
}

In our storyboard file we’ll select the segue that we have from Scene 1 to Scene 2. Change Segue to Custom, and then enter the Segue Class to match the one we just created CustomSegue.

Avoiding Source Control Nightmares

For those on a team, and while they’re getting better, storyboards are still a huge pain when it comes to source control. This is a big reason why you should aim to split your storyboards into several boards. If you can avoid it, try to make sure only one person is working on a storyboard at a time. This will prevent some nasty collisions when both of you try to push your storyboard changes to your repository. While it’s not always an easy thing to prevent, be prepared for it. This is probably the biggest reason teams and individuals avoid storyboards.

While I’ve certainly had my fair share of source control clashes with my team’s storyboards, I still feel that the speed and benefits highly outweigh the annoying fixes that I may have to do because of a clash. For the most part I make sure people avoid interface areas that I’m interacting with. But, it still happens once and a while.

Here is how I handle merge conflicts:

  • (first line of defense) Being proactive to avoid it in the first place. When working in my storyboards I aim to commit early and often while giving the heads up to any team member also working in the area. Keeping tasks small and focused helps. Don’t go pushing sweeping changes, nuking & adding controllers, when others are in the area.
  • If I hit a conflict I run through changes using a diff tool Kaleidoscope. This does require some experience understanding what is behind a storyboard. If you’ve never looked at it, now is the time: right click a storyboard > open as > source code. Observe how controllers regions are laid out (including the connection information).
  • (worst case scenario) I figure out who had the biggest/more difficult change and accept those changes, overriding the other. Then we’ll redo that change.

I’ve been on some pretty large projects. While these merge issues do pop up it’s not often severe enough that a diff tool can’t get order restored. That will come as you gain more experience. It’s going to look crazy at first.

I can’t stress the first point enough. It’s important to be proactive about it. Know what your team is up to. If you have an incredibly complex split your boards into regions.

Takeaway

Storyboards can be extremely useful, especially for those of you who are very visual. They can also give you a big speed advantage when prototyping ideas. Instead of messing with any code you can quickly throw things in place, connect them up, and start working on the logic that powers the interface.

Your Turn

What tips would you add? Have any nightmare scenarios we should watch out for?


Posted

in

by

Comments

3 responses to “10 Practical Tips for iOS Developers Using Storyboards”

  1. liuwin7 Avatar
    liuwin7

    Using storyboard is a good method to build an application‘s structure. But have you ever met the requirement that the segue’s destination is not always showed. For example, when I wanna login an app, I need get the server’s response. If my server tell me that the password for the user is incorrect, I can’t dismiss my login view, but I have set a segue to the login button. Clicking the “Login” button will lead my app to another view controller and dismiss the login view controller. How to resolve the dilemma?

    Thanks!

    1. David McGraw Avatar

      Great question liuwin7! Bear with me while I try to explain this 🙂

      Connecting segues using action buttons is great when you need to go from point A to B. But if you need to add an intermediate step (a call to a web service) BEFORE you advance to B, you do NOT create a segue from an action button. Instead, you’ll create a manual segue.

      Let’s explore a simple example.

      1. Open a storyboard with 2 view controllers
      2. Click on the first view controller
      3. At the top of the controller notice the 3 symbols (view controller, first responder, exit)
      4. Right-click + drag from the view controller symbol (yellow) to anywhere on the second view controller
      5. See a context menu pop up
      6. Select ‘show’ (which is just a standard push animation)
      7. Click on the line it generates
      8. Open the Attributes Inspector (CMD+Control+4, or View > Utilities > Show Attribute Inspector)
      9. Give your segue an identifier (like viewControllerMainSegue)

      Now you’ll dive into the code. After you make the request to your web service, you can then verify and trigger the segue (or show an error to try again).

      Manually trigger a segue by calling performSegueWithIdentifier(“the_identifier_you_used”, sender: self)

      One more thing to be aware of. You may need to pass data to the destination controller. In that case you’ll want to use the method prepareForSegue(segue:sender:). You can hunt for examples about this when you’re ready.

      Let me know if you need anything else!

      1. liuwin7 Avatar
        liuwin7

        Thank you very much, David! Your resolution is very helpful to me.
        Thanks again!

Leave a Reply

Your email address will not be published. Required fields are marked *