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
This is covered in detail in the iPad Programming Guide:
http://developer.apple.com/iphone/library/documentation/General/Conceptual/iPadProgrammingGuide/StartingYourProject/StartingYourProject.html#//apple_ref/doc/uid/TP40009370-CH9-SW3
Maybe I don’t get it:
When compiling for SDK 3.1.3 should not XCode issue a warning like ‘UIDevice’ may not respond to ‘-userInterfaceIdiom’ ?
Since you can not compile for SDK 4.0 or 3.2 but install on iPhone OS 3.1.3, can you?
s2mm3I,
There is the base SDK which you compile against and an “iPhone OS Deployment Target” which can be less than the base SDK.
This means you can add all the spiffy new features of the latest SDK with the caveat that you must make sure your app doesn’t try to access those spiffy new features when being used on older iOS’es.
Just found out myself, there seems to be a target setting ‘iPhone OS Deployment Target’ which I have never used before and not seen until SDK 4.0.
When changing that XCode gives the following hint: ‘Framework APIs that are unavailable in earlier versions will be weak-linked; your code should check for null function pointers or specific system versions before calling newer APIs. [IPHONEOS_DEPLOYMENT_TARGET]’
That is really scary! Since I do not plan to memorize all 3.2 and 4.0 API differences myself, and do not intend to make my code unreadable with ugly UI_USER_INTERFACE_IDIOM() or preprocessor macros neither.
It is a pity – the compiler really should filter such trivial errors out.