BuySellAds.com

Until Dec 3rd, 44% off all Manning books, including Barcodes with iOS! Promo code: mobicftw
Our DNA is written in Objective-C
Jump

How to make your Hybrid-App crash for sure

I was quite happy finally getting the new polished 1.1 version of SpeakerClock approved. But there was one problem: people who tried to run it on iPhones with iOS 3.1.3 started tweeting me that it crashes. I was astonished, I was not aware of using anything from 4.0, I just had to set the base SDK to 3.2 to support hybrid mode.

Then I tried out the update on my wife’s iPhone which I kept at the latest released iOS version, 3.1.3. And it crashed. So I hooked it up and had a look at the crash report. There’s something about “doesNotRecognizeSelector”. This means I am calling a method that the receiving object does not recognize. Uh Oh.

With the device still connected I switched to the console and started the app another time to see which object and what selector was unknown:

2B593A5A-7DCE-45C9-8870-A0C6B3C10F6C (seatbelt)
Sun Jun 13 10:50:13 unknown SpeakerClock[2488] : *** -[UIDevice userInterfaceIdiom]: unrecognized selector sent to instance 0x118650

And then I knew everything and my right hand involuntarily moved up to my forehead to slap it. I just had discovered a surefire way of making your hybrid app crash without Apple ever being able to “safety-reject” it. In lots of places in SpeakerClock I was using code like the following to do slightly different things dependent on what device I am running on.

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
	if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
	{
		return YES;
	}
	else
	{
		return interfaceOrientation == UIInterfaceOrientationPortrait;
	}
}

When building this code against 3.2 the compiler has no problem because the base SDK 3.2 supports the userInterfaceIdiom property of UIDevice. But setting the deployment target to 3.0 instead causes the above mentioned crash because it is nowhere to be found in the pre-3.2 iOSes.

The problem had to be fixed by asking UIDevice if it indeed sported such a method. If no, then I would refrain from calling it and instead assume that it could not have been an iPad. You know I love category extensions, and so this became another one.

UIDevice+iPad.h

@interface UIDevice (iPad)
 
- (BOOL) isIpad;
 
@end

UIDevice+iPad.m

#import "UIDevice+iPad.h"
 
@implementation UIDevice (iPad)
 
- (BOOL) isIpad
{
	if ([self respondsToSelector:@selector(userInterfaceIdiom)])
	{
		return (self.userInterfaceIdiom == UIUserInterfaceIdiomPad);
	}
	else
	{
		return NO; // cannot be iPad, OS < 3.2
	}
}
 
@end

With this in place you can safely query for iPadishness by modifying the checks to use this new method instead.

if ([[UIDevice currentDevice] isIpad])
{
	// iPad-specific code
}

Well, stupid mistake really. So obvious. But since Apple’s reviewers all test with 4.0 on their test iPhones they would never catch that.

UPDATE: Alexander Blach informed me that there’s actually a preprocessor macro that includes the selector checking.

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
	// iPad-specific code
}

Looking at the UIDevice.h header reveals that this actually does almost the same as the class extension I suggested. Only does it return UIUserInterfaceIdiomPhone in that case, my approach is to use a BOOL instead.

#define UI_USER_INTERFACE_IDIOM() ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] ? [[UIDevice currentDevice] userInterfaceIdiom] : UIUserInterfaceIdiomPhone)

Since I already submitted SpeakerClock 1.1.1 to Apple, I posted the remaining 1.1 promo codes. Grab and comment if you will.


Categories: Updates

%d bloggers like this: