Wednesday, 19 September 2012

Making apps work on iPhone 5 screen size

Here's a series of gotchas I found in attempting to convert my apps to work correctly on the new taller screen of the iPhone 5.

You need to add a new splash screen at the iPhone 5 size.

This is easy. Just create a new splash screen that's exactly 640x1136 pixels in size.
Call it Default-568h@2x.png and include it in your app.

The existence of this file is the magic key that tells iOS6 that your app is ready for iPhone 5. Without this file your app will always appear bordered.

Use window.rootViewController to add your initial view

First up, if you're building your initial views in code make sure you add your initial view to the window with window.rootViewController rather than the old way of simply adding the view as a subview.
If you don't do this, autorotating will not work at all for your app under iOS 6.

Here's an example:

navigationController = [[UINavigationController alloc] initWithRootViewController: myRootVC];
//[window addSubview:[navigationController view]]; Don't do this!
window.rootViewController = navigationController; // Do this instead!

Use viewDidLayoutSubviews rather than viewDidLoad to set up widget sizes relative to the window

If you lay out your UI in Interface Builder, and set the struts and springs properly, you might expect that inside your viewDidLoad method, your widgets will be auto-laid out to the new taller size.
They won't be! Everything will be exactly the size that you set it to be in Interface Builder at that stage.
As a result you should make sure you don't depend on the size of a widget in the viewDidLoad method. I hit this when I wanted to programatically create a view half the height of the screen.

Of course, by the time the user sees the screen everything is the right size. The issue is that this resizing takes place after the viewDidLoad - but where?

The answer is to use the viewDidLayoutSubviews method which is called after the automatic layout has finished. Override this method and insert your custom sizing.

- (void) viewDidLayoutSubviews
{
    // do clever layout logic here. super call not needed.
}
Automatically loading iPhone 5-sized images where required

You'll be familiar with the way that [UIImage imageNamed:] automatically loads @2x versions of images when running on a retina device.  Unfortunately, imageNamed: will NOT automatically load -586h@2x versions of images when running on an iPhone 5.

Sometimes this doesn't matter, for example icons and non-full screen graphics are probably the same on iPhone 4 & 5. However, if you have full-screen background images, or full-width / height background images for toolbars etc you will have problems. Your 480-high images will most likely get stretched (and will probably look horrid as a result).

You can manually check the screen size and load the right image like this:

UIImage* myImage;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
if ([UIScreen mainScreen].scale == 2.f && screenHeight == 568.0f) {
   myImage = [UIImage imageNamed:@"myImage-568h@2x.png"];
} else {
   myImage = [UIImage imageNamed:@"myImage.png"];
}  

Or better you can 'swizzle' the imageNamed function itself, and make it automatically pick up the right image (if available).
Here's an example I found on github to do exactly this (I did not write this code, copyright is with the author).



currentTime not working with the HTML5 Audio tag.. a solution!

Today I've been using the HTML5 Audio tag (in fact actually just the JavaScript Audio() object). I ran into a number of issues where clearly-documented functions simply didn't work.

Here's some sample code, with XXXXs marking what wasn't working for me (in any browser).



Now it turns out the reason these functions didn't work is not that browsers suck and this whole HTML5 is hugely overrated (my initial reaction), but was in fact that the server serving the MP3 files did not support streaming. Of course if would have been nice if somehow the audio object would warn me about this rather than sitting there doing nothing, but you can't have everything.

Anyway, now the question was how do I make my server support streaming? Do I need to sign up for some kind of premium service? Or spend weeks implementing a streaming sever?

Thankfully not. It turns out you 'simply' need to make your server support HTTP Range requests. If your server uses PHP, you're in luck. I've got a complete working example which I can share with you right now.

Note I did not write this code, I found it here and added it to gist for posterity.

Simply add this code to your PHP server, and when you're about to serve an MP3 (or OGG or whatever) call this function.



For reference, I call the function like this (where $fullPath is the absolute path to my MP3 Resource).

smartReadFile($fullPath, basename($fullPath), 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3');