Tag Archives: UIStoryboard

How to load UIStoryboards depending on screen height in iOS

A while ago I’ve written an article about how to load different storyboards depending on the screen size of an iOS device. Back in those days (2013) it was all a bit simpler than it is today, and I looked into it mainly because I loathed Auto Layout so much.

I felt it was time for an update, so here it is!

Things haven’t gotten any easier in iOS, because currently we have the following 5 screen sizes to deal with:

  • iPhone 6 Plus: 736×414 @3x
  • iPhone 6: 667×375 @3x
  • iPhone 5s: 568×320 @2x
  • iPhone 4s: 480×320 @2x
  • all iPads: 1024×768 @1x / @2x

It’s very difficult to make a UI look nice on all devices with a single UIStoryboard, and in the above video I’m showing you an alternative: design a completely different storyboard for each screen size.

The upkeep of such an app will be a little more complex, but it puts us in full control of the user experience, and not some compromise that sounds good in the Apple presentation (and sucks in reality).

In principle, the following steps are involved:

  • design various storyboards
  • detect the current device’s screen height
  • load the appropriate storyboard
  • make it “key and visible”

Detecting the screen size

If your app is set to “auto-rotate” (i.e. both portrait and landscape modes, or portrait only), the screen height will detect the longer side of the screen. This is true even if the app is started in landscape mode. Determining screen height can be done like this:

int screenHeight = [UIScreen mainScreen].bounds.size.height;
NSLog(@"Screen Height is %i", screenHeight);

Note that if you set your app to “landscape only” mode, the height parameter will return the shorter side of the screen – in which case, bounds.size.width to determine the longer portion of the screen. Thanks to Johan Grip for bringing this to my attention!

iOS 7 compatibility

Note that the screen size is orientation dependant since iOS 8 – previous versions did not take this into a account. If you must support iOS 7 and earlier it gets a lot more complicated (and I won’t cover this here – sorry).

However, this Stack Overflow discussion may help you in that case: http://stackoverflow.com/questions/24150359/is-uiscreen-mainscreen-bounds-size-becoming-orientation-dependent-in-ios8

Loading the correct UIStoryboard

With this handy integer in place, we can build a switch statement to react according to the screen height. I’m using the following method that returns my desired storyboard in my AppDelegate implementation file.

If you’re not worried about each single size, feel free to write a shorter if/then/else type method.

 

- (UIStoryboard *)grabStoryboard {
    
    // determine screen size
    int screenHeight = [UIScreen mainScreen].bounds.size.height;
    UIStoryboard *storyboard;
    
    switch (screenHeight) {
            
            // iPhone 4s
        case 480:
            storyboard = [UIStoryboard storyboardWithName:@"Main-4s" bundle:nil];
            break;
            
            // iPhone 5s
            case 568:
            storyboard = [UIStoryboard storyboardWithName:@"Main-5s" bundle:nil];
            break;
            
            // iPhone 6
            case 667:
            storyboard = [UIStoryboard storyboardWithName:@"Main-6" bundle:nil];
            break;
            
            // iPhone 6 Plus
            case 736:
            storyboard = [UIStoryboard storyboardWithName:@"Main-6-Plus" bundle:nil];
            break;
            
        default:
            // it's an iPad
            storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
            break;
    }
    
    return storyboard;
}

 

Displaying the storyboard

Inside our AppDelegate method didFinishLaunchingWithOptions, we’ll call the above method and grab the storyboard we need. To make it show up, we need to load it as the window’s root view controller and declare it “key and visible”. This is akin to the old-style way of making things appear on our iOS screens.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // grab correct storyboard depending on screen height
    UIStoryboard *storyboard = [self grabStoryboard];
    
    // display storyboard
    self.window.rootViewController = [storyboard instantiateInitialViewController];
    [self.window makeKeyAndVisible];
    
    return YES;
}

Note that using this approach will override whatever storyboard is declared in your target (under General – Deployment Info – Main Interface).

 

Demo Project

I’ve updated this project on GitHub, it shows exactly what I’m building in the screencast. Play and enjoy 🙂





How to set the Root View Controller in Xcode 6

Sometimes I delete the initial root view controller from a template and populate the storyboard with my own view controllers. As a result, that familiar arrow no longer exists.

Usually you can drag this arrow to another view controller which then becomes the new “initial view controller” – but when you delete the controller associated with it, the arrow disappears in Xcode 6.

In previous versions of Xcode the arrow just came back magically. I love “improvements” like this.

Screen Shot 2015-01-30 at 15.41.47

So how do we get it back?

Head over to Interface Builder (i.e. select your storyboard) and find the Attributes Inspector (in the right pane, fourth icon from the left). Select the view controller you’d like to become the root and tick the box that reads Is Initial View Controller.

Screen Shot 2015-01-30 at 15.40.49

Hurray. The arrow is back.





How to create an Unwind Segue in iOS 8

The Unwind Segue was introduced in iOS 6 to make retrieving data from a dismissed view controller easier. A regular Segue allows us to send data from one view controller to another, but it’s not easy to bring data back if the user has changed or added details in that view controller. That’s where an Unwind Segue comes in.

Part 1: Code

Here’s how to create one. I’m assuming we have two view controllers at our disposal, ViewController1 has initiated a standard segue, maybe passing some data. The user then changes the data, and upon saving the data, ViewController2 is dismissed and we’re going back to ViewController1.

So in ViewController1 we’ll add a method we can use as an Unwind Segue, which we later hook up in the storyboard to the Exit object. Here’s that method:

- (IBAction)backToTheStart:(UIStoryboardSegue *)segue {
    
    // grab a reference
    ViewController2 *viewController2 = segue.sourceViewController;
    
    // access public properties from ViewController2 here
}

It doesn’t matter what the method is called, just as long as it resides in ViewController1, in which you’ve imported the ViewController2.h file. What DOES matter however that our method is an IBAction and takes a UIStoryboardSegue as a parameter – otherwise Interface builder won’t recognise it, and you won’t be able to drag to the Exit object later.

Meanwhile, in ViewController2, we can make use of the following method which is often provided as a stub in new view controller classes:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    
    // do any preparation here when the segue is called
}

prepareForSegue is called just before the unwind segue (or any segue for that matter) is about to begin. You may want to read out a text field or any other values and add it to the object or variable that ViewController1 needs access to.

Part 2: Hooking up the Storyboard

With our code in place, control-drag from the button you’d like to use for initiating the segue. You can hook up multiple buttons to the same unwind segue.

Control-drag each button to the Exit object (in ViewController2):

Screen Shot 2015-01-29 at 12.00.49

As soon as the button is pressed, the unwind code in ViewController1 is executed and ViewController2 is dismissed.

Demo Project

I’ve put together a quick demo project on GitHub which is the code I’m creating the screencast above:

Further Reading





How to convert your iPhone Storyboard into an iPad Storyboard

Sometimes it’s just easier to start from an existing Storyboard rather than build everything again from scratch. Especially so when you want to create an iPad version of your iPhone app.

When you change your deployment info to Universal, Xcode even offers to copy your existing storyboard for you. That’s really nice – but even though it behaves like an iPad storyboard, when you open it in Interface Builder it still looks like you’re creating an iPhone layout. This makes re-arranging things difficult.

Help it at hand with a simple tweak. Under the hood, a Storyboard is just an XML file and as such as a plethora of key/value pairs we can change by hand.

First, make sure you display your layouts in “3.5inch” mode. You can do that by clicking that little icon at the bottom of Interface Builder:

Screen Shot 2014-05-19 at 11.20.25

Next head over to the File Inspector and right-click your storyboard, then select “Open as Source Code”. This will display all that XML code.

Screen Shot 2014-05-19 at 11.08.32

In the second line of the document you’ll find a very long line – somewhere in it you’ll find something like this:

targetRuntime="iOS.CocoaTouch"

Simply change that line to

targetRuntime="iOS.CocoaTouch.iPad"

This will make Interface Builder display your storyboard with iPad characteristics. All we now have to do is to make it look like a Storyboard again rather than XML code – so head back and right-click on the file again, then select “Open as Interface Builder – iOS Storyboard”.





How to load a different storyboard depending on screen size in iOS

As much as I appreciate the idea of Auto Layout, I don’t think it likes me very much. In turn, I don’t like Auto Layout very much. The pain and hassle it takes to make several UI elements fit well on a 4 inch screen as well as a 3.5 inch screen regularly drives me crazy. On paper it’s a great idea – but in reality not fit for prime time.

Screen Shot 2013-12-31 at 08.56.22

It’s not a coincidence that we can choose completely different storyboards for iPads and iPhones. Let’s take the same approach when dealing with the two different iPhone screen sizes by loading different storyboards.

Here’s how: First, finish one version of your storyboard. Next select it and head over to File – Duplicate. Give the second one an appropriate name, then tweak your UI elements so that they look nice on the other screen size.

Now add this to your AppDelegate.m:

- (UIStoryboard *)grabStoryboard {

    UIStoryboard *storyboard;

    // detect the height of our screen
    int height = [UIScreen mainScreen].bounds.size.height;

    if (height == 480) {
        storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        // NSLog(@"Device has a 3.5inch Display.");
    } else {
        storyboard = [UIStoryboard storyboardWithName:@"Main-4in" bundle:nil];
        // NSLog(@"Device has a 4inch Display.");
    }

    return storyboard;
}

This method returns the following UIStoryboard file:

  • Main.storyboard on 3.5inch devices
  • Main-4in.storyboard on 4inch devices

Now we need to make sure it gets loaded appropriately. We’ll take care of it in AppDelegate.m like so:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIStoryboard *storyboard = [self grabStoryboard];

    // show the storyboard
    self.window.rootViewController = [storyboard instantiateInitialViewController];
    [self.window makeKeyAndVisible];

    return YES;
}

This overrides the default storyboard that’s declared in your project settings. You can expand the above method by checking which iOS Version the device is running, and of course returning an iPad storyboard too.

In your face, “Auto Layout”!

Demo Project

A demo project is available on GitHub:

Update 2015

I’ve updated this article to incorporate all iPhone sizes, and I’m explaining it all in a video:





How to load different Storyboards for different versions of iOS in Xcode 5

No matter how hard I’ve tried to tweak my Storyboard, there are always areas that only look well on one particular version of iOS. There is no “one Storyboard fits all” approach.

The easiest option is to have one Storyboard for iOS 6 and another for iOS 7 – then everybody’s happy. Question is, how do we tell our app which one to load depending on the iOS version our device is running?

Here’s how: AppDelegate.m to the rescue! Let’s determine it in applicationDidFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // load storyboard depending on iOS version

    UIStoryboard *storyboard;
    
    if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
        // Load resources for iOS 6.1 or earlier
        storyboard = [UIStoryboard storyboardWithName:@"OldStoryboard" bundle:nil];
        
    } else {
        // Load resources for iOS 7 or later
        storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
        
    }
    
    // show the storyboard
    self.window.rootViewController = [storyboard instantiateInitialViewController];
    [self.window makeKeyAndVisible];
    
    return YES;
}

This will override whichever Storyboard is defined in the target (under Deployment Info, Main Interface).