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?
Working around SLComposeViewController in iOS 5
Scroll down to the share button’s IBAction
method -shareButtonTapped:
and rework the implementation as so:
- (IBAction)shareButtonTapped:(id)sender {
//Email, Facebook, Twitter, Clipboard
self.actionSheet = [[UIActionSheet alloc] initWithTitle:@"Share"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:nil];
[self.actionSheet addButtonWithTitle:@"E-mail"];
if ([SLComposeViewController class]) {
[self.actionSheet addButtonWithTitle:@"Facebook"];
}
[self.actionSheet addButtonWithTitle:@"Twitter"];
[self.actionSheet addButtonWithTitle:@"Clipboard"];
[self.actionSheet showFromBarButtonItem:self.shareButton animated:YES];
}
In this new implementation, the SLComposeViewController
is tested to see if it exists. If so, then the Facebook share button is shown, otherwise it is not. That’s because SLComposeViewController
is required for Facebook sharing.
But what about Twitter? That’s a bit easier since even though Twitter native support is now part of the Social framework, the same code did exist in iOS 5, but just under the specific Twitter framework.
The functionality will remain the same, but your app will now use TWTweetComposeViewController
instead of SLComposeViewController
for both iOS 5 and iOS 6.
To add the Twitter framework, go to Build Phases -> Link Binary With Libraries for your target. Add Twitter.framework
as a “Required” library. Make sure not to remove the Social framework because it is still going to be used for native Facebook sharing in iOS 6.
Go back to RWDetailViewController.m and add the following import statement at the beginning of the file to support the framework you just added:
#import <Twitter/Twitter.h>
Open RWDetailViewController.m, find actionSheet:clickedButtonAtIndex:
. Then look for the following bit of code:
RWRageFaceViewController *rageFaceViewController = self.pageViewController.viewControllers[0];
NSString *imageName = rageFaceViewController.imageName;
UIImage *image = [UIImage imageNamed:imageName];
…and replace it with the following code:
NSString *imageName = self.imageNames[self.index];
if (self.pageViewController) {
RWRageFaceViewController *rageFaceViewController = self.pageViewController.viewControllers[0];
imageName = rageFaceViewController.imageName;
}
UIImage *image = [UIImage imageNamed:imageName];
Essentially, this ensures that you share the correct image in each OS version. If you are on iOS 5, there will only be one image per RWDetailViewController
which you access through self.imageNames[self.index]
.
However, if the app is running on iOS 6 there will be a UIPageViewController
and the image that you share is determined by whatever view controller UIPageViewController
is displaying at the moment.
If you look at the rest of this method you will see that it uses the index of the button tapped to determine the behaviour. This logic no longer works because Facebook may or may not be present, so you’ll use the name of the button instead.
Add the following code to the start of actionSheet:clickedButtonAtIndex:
:
NSString *buttonTitle = [actionSheet buttonTitleAtIndex:buttonIndex];
And then replace the if-statement conditions in the same method with the following:
if ([buttonTitle isEqualToString:@"E-mail"]) { /* E-mail*/
// ... same content as before ...
}
else if ([buttonTitle isEqualToString:@"Facebook"]) { /* Facebook */
// ... same content as before ...
}
else if ([buttonTitle isEqualToString:@"Twitter"]) { /* Twitter */
// ... same content as before ...
}
else if ([buttonTitle isEqualToString:@"Clipboard"]) { /* Copy to Clipboard */
// ... same content as before ...
}
This is changing from using the button index to determining which button was pressed by its title. This is required now because the button index no longer maps to exactly which button was pressed because on iOS 5 there are a different number of buttons to that on iOS 6.
In actionSheet:clickedButtonAtIndex:
, scroll down to the else-if
statement that handles Twitter sharing and modify the implementation as follows:
else if ([buttonTitle isEqualToString:@"Twitter"]) { /* Twitter */
if ([SLComposeViewController class] && [SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) {
SLComposeViewController* twitterVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
[twitterVC setInitialText:@"Check out this Rage Face at raywenderlich.com via @RWRageFaces"];
[twitterVC addURL:[NSURL URLWithString:@"http://www.raywenderlich.com"]];
[twitterVC addImage:image];
[self presentViewController:twitterVC animated:YES completion:nil];
}
else if ([TWTweetComposeViewController class] && [TWTweetComposeViewController canSendTweet]) {
TWTweetComposeViewController *twitterVC = [[TWTweetComposeViewController alloc] init];
[twitterVC setInitialText:@"Check out this Rage Face at raywenderlich.com via @RWRageFaces"];
[twitterVC addURL:[NSURL URLWithString:@"http://www.raywenderlich.com"]];
[twitterVC addImage:image];
[self presentViewController:twitterVC animated:YES completion:nil];
}
else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"No Twitter Accounts"
message:@"Please add a Twitter account by going to Settings > Twitter"
delegate:nil
cancelButtonTitle:@"Cancel"
otherButtonTitles:nil];
[alertView show];
}
}
The above code ensures that TWTweetComposeViewController
is used on iOS 5 and the newer, SLComposeViewController
is used on iOS 6 where it is present. Even though TWTweetComposeViewController
is due to be deprecated, you need to use it on iOS 5 as that’s the only way to achieve Twitter sharing natively.
Build & run the app and go to share a rage face. W00t! No longer can you click Facebook and all the existing share options still work, including Twitter.
That’s the end of the changes required to ensure RWRageFaces works on iOS 5; it doesn’t support all the bells and whistles that iOS 6 offers, but it neatly handles the difference between the two apps by disabling or sidestepping features as necessary.
Deploymate Sanity Check
You’ve fixed all the compatibility issues in your app — or have you? An old rule of testing says that you can’t fix the bugs that don’t yet know they exist. :] Before shipping, it’s a good idea to give your app a run through Deploymate.
Deploymate is a static analyzer for OS X created by Ivan Vasic (@ivanvasic). It scans your source code and reports on any unsupported APIs based on your deployment target; it’s useful as a quick sanity check before submitting to the App Store. You can download Deploymate here.
Note: The demo is functional but it doesn’t report all API problems, just random ones — so don’t rely on the demo version for production releases!
Note: The demo is functional but it doesn’t report all API problems, just random ones — so don’t rely on the demo version for production releases!
When you first open Deploymate, you’ll see the following Launch screen:
Select Open existing Xcode project and navigate to the RWRageFaces.xcodeproj file from the RWRageFaces project. Open the project file and a rather familiar-looking UI will be presented, as shown below:
Verify that your “Deployment OS” is set to the correct target (iOS 5 in this case) and click Analyze.
Deploymate takes anywhere from a few seconds to a few minutes to scan through your entire project; the total time really depends on the number of files to analyze. When it’s finished the analysis, the results screen will look something like the following:
Whoa — hang on a second. Why are there 26 unavailable API calls if you just fixed all the backwards compatibility issues in RWRageFaces?
Remember that Deploymate is a static analyzer, so it’s just doing a compile-time check for unsupported symbols. As of this writing, it cannot follow your conditional logic to see what is and isn’t going to be executed at runtime.
Click on any of the issues on the left side, and Deploymate will show you the code that raised the issue, as demonstrated in the screenshot below:
In this particular case Deploymate is complaining about NSForegroundColorAttributeName
, which was introduced in iOS 6.
Notice that NSForegroundColorAttributeName
is wrapped in an if statement that checks if UILabel
can accept attributed strings, so it’s won’t be called on iOS 5 devices.
Take the time to step through every issue and make sure you’ve handled each case in a way that’s not going to cause a crash or other undefined behavior at runtime.
One last thing to keep in mind with Deploymate in its current form doesn’t scan NIB or storyboards files; so it would not have prompted you to turn off Auto Layout or removing the embed segue as you did earlier to correct the crashing behavior.