Saturday, April 3, 2010

Converting iPhone Apps to Universal Apps

Well, the NDA has finally lifted, so we can start talking about iPhone SDK 3.2 and the iPad. The logical starting place seemed to be how to convert your existing applications into a "Universal App" that runs natively both on the iPad and the iPhone/iPod touch. Now, a lot of you have likely already had to figure this stuff out so you could get your updated app on the store today, but for those who didn't go the early adopter route, let's take a few minutes to look at the process. It's pretty straightforward but there are a few gotchas.
Note: There are some additional things you should know, so read this post also before tackling your update.

Targeting All Devices


The first thing you have to do is identify that you want to build your existing application as a universal application. For this article, I'm using the Xcode project from OpenGL ES Particle Generator Application, but I'll try to keep the information general. Note: the following step is not needed if you use Xcode's Update Project Target for iPad option talked about here.

Bring up your Project Info window in Xcode by either double-clicking on your project's root node in the Groups & Files pane or selecting Edit Project Settings from the Project menu and then navigate to the Build tab. Now, the change we're about to make needs to be made to all configurations, so make sure that the Configuration popup menu is set to All Configurations, otherwise you'll only make the change on one configuration.

We need to change a setting called Target Device Family, so type Target into the search bar, or just search for that entry manually (it'll be under the Deployment heading). Right now, it should look like this:

Screen shot 2010-04-01 at 10.09.58 AM.png


See how it says iPhone? Yeah, you know what to do. Click on it and change it so it reads iPhone/iPad, like so:

select_target.png


Good! now you're done, right? Most likely, no.

Auditing for Hardcoded Sizes


The next thing you're going to want to do is audit your application to see if you hard-coded the screen size anywhere in your application. You shouldn't have hardcoded those values, but let's face it, we've all done it. A Project Find (⌘⇧F) for 320 and 480 and that should turn up any of those hardcoded values. In the Particle Generator code, I did it in only one place, in code that creates a UIImage of the OpenGL view. The line of code where I did it looks like this:
    CGImageRef imageRef = CGImageCreate(320, 480, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

Usually, the fix for this will be obvious. Instead of hardcoding, you want to pull the width and height from the OpenGL view. The code where I did it actually exists on the GLView class, so I can fix it like so:
    CGImageRef imageRef = CGImageCreate(self.frame.size.width, self.frame.size.height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

But, what if what you've hardcoded is the actual size of the view? Then you need to pull the size from the main screen instead of the view. That's easy enough to do.
    UIScreen *screen = [UIScreen mainScreen];
[myView setFrame:[screen applicationFrame]];


Dealing with Different Window Sizes


Most likely your application's one instance of UIWindow is contained in your MainWindow.xib file that gets loaded automatically. Most likely, that Window is hardcoded to 320x480. Now, you might think that you can just go into Interface Builder and set the autosize attributes for the window and it will get resized for you at launch. You would be wrong. There is no automatic check to make sure your window is the right size.

You have to make sure that the window is the right size for the device you're running on. There are, basically, two ways of doing that. If your application is such that you just need to resize the window and your autosize attributes will take care of making everything look nice, then you can just handle this programmatically in applicationDidFinishLaunching: by setting the window's size to the size of the screen, less the status bar andy any other objects controlled by the iPhone OS (this is known as the Application Frame). Doing this looks almost exactly like setting the size of the view above:
    CGRect  rect = [[UIScreen mainScreen] bounds];
[window setFrame:rect];

Now, this is actually a good approach for the Particles application because it has one full-screen view. However, the iPad and the iPhone are really different devices, and there are several UI components available on the iPad that aren't available (at least yet) on the iPhone, such as split views and pop up views. For many applications, especially complex applications using a lot of UIKit views and controls, you're probably going to want to provide completely different NIB file based on which device on which the code is running.

Info.Plist Device-Specific Entries


The one really important nib file in every iPhone application is, of course, MainWindow.xib, and there has to be a way to tell your application to use a different MainWindow.xib for different devices. In fact, there is. For each key that Info.plist supports, such NSMainNibFile, which is used to specify the name of the application's main nib file, you can now specify device-specific entries. If you provide a device-specific entry for the device the application is currently running on, it will use the device-specific value, otherwise it will just use the normal value.

Device-specific keys are exactly the same as the original or default key except the key name is followed by a tilde (~) and then the name of the device in all-lowercase letters. So, to tell our application to load a different nib file for the iPad, we can add a key called NSMainNibFile~ipad and then specify the name of the nib file to use when launching on an iPad. For the iPhone and iPod touch, it will continue to use the default value, MainWindow.xib, but for the iPad, it will use the nib file you've specified in the new, device-specific key.

You can add a new version of MainWindow.xib to your project by selecting the Resources group and choosing Add New File from the File menu. From the New File Assistant, select User Interface from under the iPhone OS, then select Application XIB, and make sure you select the right device in the Product drop-down.

Screen shot 2010-04-01 at 10.43.29 AM.png


Make sure you remember to connect all the outlets and actions in this new nib to the same outlets and actions you used in the other nib. Remember, only one of the application nibs will be loaded, so there's no conflict.

For any key in the Info.plist file, you can use this same technique to override the default value with a device specific. You could, for example, have the iPhone version start in Portrait and the iPad version start in landscape, like so:
    ...
<key>UIInterfaceOrientation</key>
<string>UIInterfaceOrientationPortrait</string>
<key>UIInterfaceOrientation~ipad</key>
<string>UIInterfaceOrientationLandscapeLeft</string>
...

Programmatically Determining Device


If you have code that needs to vary depending on whether it's running on the iPad or iPhone/iPod touch, Apple has provided a new macro called UI_USER_INTERFACE_IDIOM() that will tell you that. There are currently two values defined, UIUserInterfaceIdiomPhone and UIUserInterfaceIdiomPad, and this macro will return the value that corresponds to the device being run. So, for example, if you needed to push a view controller onto the navigation stack, but wanted a different nib used for the iPad than the iPhone, you might do this:
    MyController *controller = nil;

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
controller = [[MyController alloc] initWithNibName:@"MyiPadNib" bundle:nil];
else
controller = [[MyController alloc] initWithNibName:@"MyiPodNib" bundle:nil];

[self.navigationController pushViewController:controller animated:YES];
[controller release];

If you need finer-grain control, and need to know exactly which device, there's no official, supported way to determine the exact device. The vast majority of the time you you think you need to know the device, you don't actually need to know the device, you just need to know which features are supported. Even though you can find code around the web that will determine the device based on UIDevice, you really shouldn't base your logic on that because such code can be fragile since you don't know what future devices will exist, or what features they will have.

In cases like the Image Picker, Apple provides a way to determine which features are available on your device, such as whether there's a camera, and whether that camera supports video. When Apple hasn't provide a specific check or test, what you can do is use NSClassFromString(), which (as is probably obvious from the name) creates a Class instance based on the name of a class contained in a string. If this returns nil, then you know the class you're asking about isn't available. You can wrap your code that uses classes that aren't available everywhere in these checks and make code that works correctly on all devices, and will continue to do so in the future (for the most part - it's never possible to 100% future-proof code). Here's an example of checking for the existence of the UISplitViewController, which is a new class only available on the iPad:
    Class splitViewController = NSClassFromString(@"UISplitViewController");
if (splitViewController)
{
UISplitViewController* mySplitViewController = [[splitVCClass alloc] init];
// ... configure, use, then release
}

You can do something similar with C functions by checking if the function is NULL For example, one of the frameworks added with iPhone SDK 3.2 is CoreText.framework. If we wanted to use the function CTFontCreateWithName() to create a new font using that framework, we could wrap the logic in an interface idiom check, like above, or we could just check to see if the function we want to use exists by seeing if the symbol CTFontCreateWithName is NULL at runtime, like so:
    if (CTFontCreateWithName != NULL)
CTFontFontRef myFont = CTFontCreateWithName(@"Comic Sans", 14.0, NULL);


Go, Go, Gadget iPad, Go!


Well, that pretty much covers the basics you'll need to convert your existing iPhone apps to Universal Apps. The more complex your app, the more likely you'll want to consider doing separate iPad and iPhone applications. I'll show how to add another target to your Xcode project so you can generate two applications from the same project in a future post. For many apps, however, this should be enough to get you porting away, so port away!

Friday, April 2, 2010

Ad Hoc Distribution, Android vs. iPhone

I've spent a fair few words on this blog discussing my perception of the Android SDK. To sum it all up simply, my overall opinion is that programming for the Android SDK is good, but nowhere near as good, nor as fun, as programming for iPhone SDK. Although Android has some places where it shines, there are relatively few where it outshines the iPhone SDK.

There is one, however. One where the experience is worlds better in the Android camp: ad hoc distribution.

When you do contract programming, ad hoc distribution is a big part of your life. A lot of people who hire others to write software aren't programmers themselves, so we have to have a way to get the applications we develop to our client for their review and for testing. On the iPhone, we have to go through this relatively convoluted process where we create an ad hoc distribution certificate then create an ad hoc provisioning profile that ties the application to certain, specific devices based on a unique identifier called a UDID. The client then has to install the provisioning profile on their phone, and then install the app.

When it works, it's not too terrible. It's a bit of a pain in the ass, but it's not too awfully time-consuming.

The problem is when it doesn't work. When a client reports that an app won't install, trying to remotely figure out why can be really tedious and time-consuming and the process creates a negative impression of the platform in the eyes of the client. And there are many reasons it can go wrong, and it does go wrong way too often in my experience. If the application bundle gets changed in any way, code signing fails and the app won't launch. If the provisioning profile doesn't get installed properly, installation of the app fails. Often, when the problem is finally fixed, we have no idea what we did that fixed it.

And we developers are left holding the bag. We're left spending hours and sometimes even days trying to get the app working on the client's device. Usually, that time we spend figuring out what the hell is going on is unbillable. When a client isn't a programmer, especially when a client isn't particularly tech-savvy, the whole process can be extraordinarily painful and frustrating both for us and for our clients. On top of that, we're limited to 100 devices that we can even do this process with. With several models of iPhone and iPod Touch, plus the iPad and the fact that most developers have multiple clients in the course of a year, we have the added hassle of having to manage our devices so we don't exceed our limit.

On Android? I drop the compiled app into my Dropbox folder and e-mail the client the URL to it. I instruct them to tap the link from their phone. That's it. It just works. No hassles, no worries. There's a one-time instruction I have to give them that involves tapping a checkbox in their phone's settings to allow third-party apps, but after that, it's just the URL. The user still has control of whether it gets installed, so it's relatively safe, but there's no provisioning profile, no iTunes requirement, no worries about the application getting corrupted or accidentally changed in transit. No angry customer calls or e-mails.

In most ways, I wish Android was more like the iPhone. In this way, I really, really wish the iPhone was more like Android. This whole process lacks the polish and ease of use that I ordinarily associate with Apple products, and after two years of developer complaints, it has improved a bit, but not nearly enough.

Thursday, April 1, 2010

Newspaper now in your iPhone with Kiosko.net 1.0-iPhone visual daily press dictionary

Kiosko.net is a new app for your iPhone/iPod touch that gives you opportunity to access the most important latest newspaper headlines to the palm of your hand wherever you are.
Kiosko.net is released by KTS S.C. and with this app you can read the front pages from nearly 1200 newspapers around the world.
You can search newspapers by name or by region with very few finger taps, meanwhile the newspaper database is stored locally in the application for faster search results and ability to read headlines without being connected to the network.
In addition, you can add to favourites a list of the newspapers you read most often and access them directly on every app run.



Requirements:
* iPhone and iPod touch
* Requires iPhone OS 3.0 or later
* 2.9 MB

Availability:
Kiosko.net 1.0 is now available worldwide for an introductory price of $1.99. App can be downloaded from the iTunes App Store.