Supporting Multiple iOS Versions and Devices
There are many different iOS versions and devices out there in the wild. Supporting more than just the latest is often necessary since not all users upgrade immediately. This tutorial shows you how to achieve that goal. By Pietro Rea.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Supporting Multiple iOS Versions and Devices
50 mins
- iOS Versions: An Overview
- Getting Started
- Why Bother Supporting Multiple iOS Versions?
- Deployment Target vs. Base SDK
- Identifying Backwards Compatibility Issues
- Solving Backwards Compatibility Issues
- Supporting iOS 5 in RWRageFaces
- Working around Auto Layout in iOS 5
- Working Around Embed Segues in iOS 5
- Working Around NSAttributedString in iOS 5
- Working Around Page Transitions in iOS 5
- Working around SLComposeViewController in iOS 5
- Deploymate Sanity Check
- Supporting Multiple Devices
- Device Collection Winners
- Where To Go From Here?
Identifying Backwards Compatibility Issues
Backwards compatibility problems are so common it’s almost guaranteed you’ll hit them at some point in your iOS development career. Fortunately, there are several tools at your disposal that can help you quickly identify potential problems:
Older iOS devices
The best way to test backwards compatibility of your apps to run them on older iOS devices. It’s difficult to revert the iOS version on a device, so if at all possible don’t update the OS that shipped with your device. With this strategy, you’ll eventually form a collection of legacy iOS devices for testing and development.
Note: Ray asked his Twitter followers to submit photos of their iOS collections. Check out the “winners” at the end of this tutorial; the largest collection boasts over 15 iOS devices!
Note: Ray asked his Twitter followers to submit photos of their iOS collections. Check out the “winners” at the end of this tutorial; the largest collection boasts over 15 iOS devices!
iOS simulator
If you can’t get your hands on a physical device, the next best thing is testing your app in the simulator running an earlier version of iOS. Xcode 4.6 supports iOS versions back to 5.0 which should be sufficient for most of your backwards compatibility tests. However, if you really need to run your app against a version earlier than 5.0, you can get there with a bit of hackery and tweaking.
Apple docs
Apple’s official documentation is incredibly useful when you need to know which version of iOS introduced a particular class or method. For example, look up UITableView
and you’ll see at the top of the document that UITableView
was introduced in iOS 2.0.
If you need to know when a particular property or method was introduced, you can find this in the “Availability” subheading, as shown in the screenshot below:
Header Files
Sometimes the best documentation of methods and classes is contained right in the source code.
Simply Command-click the symbol you want to inspect, and Xcode will take you to its header file, where you will find the information you need along with the parameters of the preprocessor macros NS_AVAILABLE_IOS()
and NS_CLASS_AVAILABLE_IOS()
. If a class or method declaration doesn’t use either of those availability macros, you can assume that they won’t cause any compatibility issues.
Note: ALT + click on a method will display a popover that contains these same details along with other information about the method.
Note: ALT + click on a method will display a popover that contains these same details along with other information about the method.
API diffs
Apple publishes an exhaustive list of everything that was added or modified in each release of iOS. As an example, the iOS 6.0 API diff documents all the changes and additions associated with iOS 6.
API diffs won’t necessarily help you with in the nitty-gritty details of development, but they’re recommended reading to any developer who needs to be aware of issues involved with supporting older versions of iOS.
Deploymate
Wouldn’t it be great if Xcode compared your code against your deployment target and alerted you about any unsupported APIs? Until that day comes the Mac application Deploymate will do the job for you.
Deploymate is a static analyzer that scans your source code and warns you when it encounters unsupported or deprecated APIs. You’ll learn more about Deploymate in the project section of this tutorial, which includes a detailed demo of Deploymate in action.
MJGAvailability
Tutorial team member Matt Galloway, has released a simple header file that solves the problem of knowing when you’re using APIs that are not available in the deployment target. It solves the problem by tricking the compiler into thinking that such APIs are deprecated. Of course, they’re not, but the compiler thinks they are and warns as necessary.
Solving Backwards Compatibility Issues
Once you have identified the backwards compatibility issues in your app, the next step is to figure out how to fix them. Each new version of iOS introduces new frameworks, classes, methods, constants and enumeration values — and there’s a specific strategy to deal with each of these.
Unsupported frameworks
Linking against a framework that doesn’t exist in your deployment target will just cause the dynamic linker to crash your app. To solve this, you mark unsupported frameworks as “Optional” in your project settings.
To do this, select your project under the “Targets” section, and open up Build Phases -> Link Binary With Libraries. Next to each framework you can specify either Required or Optional. Selecting Optional will weak link the framework and solve your compatibility issue.
Note: You can read more about weak linking in Apple’s Framework Programming Guide.
Note: You can read more about weak linking in Apple’s Framework Programming Guide.
Unsupported classes
Sometimes you want to use a class that exists in your base SDK, but not in your deployment target. To do this you need to check the availability of this class at runtime to avoid crashing your app. It crashes because this is what the Objetive-C runtime will do if you try to use a class that doesn’t exist. As of iOS 4.2, classes are weakly linked so you can use the +class
method to perform the runtime check. For example:
if ([SLComposeViewController class]) {
//Safe to use SLComposeViewController
} else {
//Fail gracefully
}
Unsupported methods
Similarly, if you’re using a method in your base SDK that doesn’t exist in your deployment target, you can avoid nasty crashes by using a little introspection.
The methods -respondsToSelector:
and +instancesRespondToSelector:
will both do the trick, as shown in the code examples below:
if ([self.image respondsToSelector:@selector(resizableImageWithCapInsets:resizingMode:)]) {
//Safe to use this way of creating resizable images
} else {
//Fail gracefully
}
The same goes for verifying the existence of class methods, except you call respondsToSelector:
on the class itself, like so:
if ([UIView respondsToSelector:@selector(requiresConstraintBasedLayout)]) {
//Safe to use this method
} else {
//Fail gracefully
}
Note: If you want to check for the presence of a certain property on a class, then you can do so by testing that instances respond to the property getter or setter.
For example, to check if UILabel
can take an attributed string via its attributedText
property (new in iOS 6), perform introspection against the implicit setter method @selector(setAttributedText)
.
Note: If you want to check for the presence of a certain property on a class, then you can do so by testing that instances respond to the property getter or setter.
For example, to check if UILabel
can take an attributed string via its attributedText
property (new in iOS 6), perform introspection against the implicit setter method @selector(setAttributedText)
.
Unsupported constants/C functions
Sometimes a constant value is the missing piece of the deployment target; it usually takes the form of an extern NSString *
or a C function. In this case, you can perform a runtime check against NULL to determine if it exists.
For example, the C function ABAddressBookCreateWithOptions(...)
was introduced in iOS 6 but can still live safely in your iOS 5 app like so:
if (ABAddressBookCreateWithOptions != NULL) {
//Safe to use
}
else {
//Fail gracefully
}
The same approach applies to constants. For example, iOS 4.0 introduced multitasking support. If you wanted to check for the existence of UIApplicationWillEnterForegroundNotification
, you would simply validate it as so:
if (&UIApplicationWillEnterForegroundNotification) {
//Safe to assume multitasking support
}
else {
//Fail gracefully
}
For further proof, take a look at UIApplication.h in Xcode. You’ll see that UIApplicationWillEnterForegroundNotification
is simply an extern NSString *
declared at the bottom of the document.
When your application is loaded into memory, these strings are initialized and stay resident in memory. The &
operator gets the string’s memory address. If the memory address is not equal to nil
, then the NSString
is available, otherwise it’s not available and your code will have to work around that fact.
Note: The mechanism that makes this work is weak linking, which was described earlier. When a binary is loaded the dynamic linker replaces in the app binary any addresses of things (functions, constants, etc) in dynamically loaded libraries. If it’s weakly linked then if the symbol in the library is not found then the address is set to NULL.
Note: The mechanism that makes this work is weak linking, which was described earlier. When a binary is loaded the dynamic linker replaces in the app binary any addresses of things (functions, constants, etc) in dynamically loaded libraries. If it’s weakly linked then if the symbol in the library is not found then the address is set to NULL.
Unsupported enumeration values
Checking for the existence of enumeration or bit mask values — the kind that you would find inside an NS_ENUM
or NS_OPTIONS
declaration — is incredibly difficult to check at runtime. Why?
Under the hood, an enumeration is just a method of giving names to numeric constants. An enum is replaced with an int
when compiled, which always exists!
If you’re faced with the situation of needing to see if a certain enumeration exists, all you can do is either explicitly check the OS version (which isn’t recommended) or alternatively check against another API element that was introduced at the same time as the new enumeration value.
Note: Whichever way you do this, be sure to add adequate comments to any such code and consider wrapping the code in a dedicated compatibility helper.
Note: Whichever way you do this, be sure to add adequate comments to any such code and consider wrapping the code in a dedicated compatibility helper.
Explicit iOS version checking
You should generally stay away from checking explicit iOS versions but there are specific situations where it’s unavoidable. For example, if you need to account for a bugfix in a previously available method, use the following line of code to return the OS version:
NSString *osVersion = [[UIDevice currentDevice] systemVersion];
You can use NSString’s compare:options:
method, passing NSNumericSearch
as the options to compare OS versions. This takes into account the fact that 6.1.1 is greater than 6.1.0. If you converted them to floats first and then used standard arithmetic things would go wrong because both would parse as the number 6.1.
Note: You can find out more about this topic in Apple’s SDK Compatibility Guide.
Note: You can find out more about this topic in Apple’s SDK Compatibility Guide.