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?
Supporting iOS 5 in RWRageFaces
Okay – time to put this theory into action and add iOS 5 back support capability in RWRageFaces!
Click on your project file, and under “Info” change the iOS Deployment Target to iOS 5.0, as shown below:
Select the iPad 5.0 or iPad 5.1 Simulator on the top left of your screen, as shown below:
Build and run your app, and…crash!
The app immediately crashes at launch with the following error message:
dyld: Library not loaded: /System/Library/Frameworks/Social.framework/Social
dyld is the name of the dynamic linker in iOS and Mac OSX. The app crashes because it uses the Social framework for sharing on Facebook and Twitter — but the Social framework was introduced in iOS 6, and dyld can’t find it in the iOS 5 simulator.
To fix this, first select your app’s target in Xcode. Select the Build Phases tab, and under Link With Binary Libraries, make sure Social.framework
is set to Optional instead of Required, as shown below:
Build and run your app, and…another crash? What’s going on here?
The error message displayed is different this time; maybe it offers up some clues:
NSInvalidUnarchiveOperationException, reason: ‘Could not instantiate class named UICollectionView’
Ah, that’s the issue. The app is trying to use UICollectionView
to create the grid of rage faces; the problem is that collection views were not added until iOS 6 and the simulator only knows about objects in iOS 5.
Note that the exception is related to unarchiving; this is a clue that iOS is trying to unarchive a UICollectionView
from either a NIB file or a storyboard and failing.
Open MainStoryboard.storyboard in Xcode. You can see that the initial view controller contains a UICollectionView
, as shown below:
To get around this problem, you’re going to use an open source library authored by Peter Steinberger called PSTCollectionView
. Download the library from Github.
Note: The link above downloads the 1.1.0 release of PSTCollectionView. Feel free to download a more up-to-date version if one exists.
Note: The link above downloads the 1.1.0 release of PSTCollectionView. Feel free to download a more up-to-date version if one exists.
Unzip the library and drag the entire PSTCollectionView directory to the frameworks directory in Xcode, as shown below:
As per the instructions found on Github, you also require the QuartzCore framework to work with this library. Click on your target and add QuartzCore on the Build Phases tab, under the Link With Binary Libraries section and make it Required, as so:
PSTCollectionView
is a faithful implementation of UICollectionView
that you can use in deploys all the way back to iOS 4.3.
Helpfully, PSTCollectionView
also has a mode whereby you can make it detect if the device is running iOS 6 and if so use UICollectionView
, otherwise use PSTCollectionView
‘s own classes. That’s very neat.
Let’s use this shiny toy!
Add the following import to RWGridViewController.m:
#import "PSTCollectionView.h"
Next, go through RWGridViewController.m and find and replace all occurrences of UICollectionView with PSUICollectionView. That includes replacing all occurrences of UICollectionView at the start of words as well, meaning you will make the following replacements:
- UICollectionView becomes PSUICollectionView
- UICollectionViewDataSource becomes PSUICollectionViewDataSource
- UICollectionViewDelegateFlowLayout becomes PSUICollectionViewDelegateFlowLayout
- UICollectionViewCell becomes PSUICollectionViewCell
- UICollectionViewLayout becomes PSUICollectionViewLayout
Once you’ve prefixed all appearances of UICollectionView*
with “PS”, build and run your project.
Once again, Xcode crashes in a spectacular manner:
This time, the error reads:
*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named NSLayoutConstraint’
Right…NSLayoutConstraint
is used by Auto Layout which wasn’t introduced until iOS 6.
Working around Auto Layout in iOS 5
To fix this issue, open the main storyboard and click on the File Inspector. Click on the Use Autolayout checkbox to disable it, as shown below:
Build and run your app and…and…it starts normally! If you experienced a crash due to an exception related to UICollectionView
, go back and double-check that you’ve prefixed everything with “PS”.
Rotate your device, or if you’re on the simulator, press Command+Left/Right arrow.
Hm. The app doesn’t rotate like it used to. That’s because iOS 5 and iOS 6 call different rotation methods.
Add the following code to RWGridViewController.m below viewDidLoad
:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
This method is an old iOS 5 method that tells UI Kit what orientations are supported for this view controller. It wasn’t necessary when the app supported just iOS 6, but it’s now necessary to support iOS 5 as well.
Build and run your app and try to rotate the device or simulator again.. The app rotates properly now — but the navigation bar isn’t in the right spot, as shown in the image below:
By turning off auto layout, the navigation bar uses its autosizing mask; that is, using springs and struts. Looks like you’re not done yet!
Head back to the main storyboard, and select the navigation bar in the Grid View Controller Scene. In the Size Inspector on the right, click on the top strut to turn it on. Do the same for the nav bar in RWDetailViewController
Next, turn on flexible width and height for the collection view in RWGridViewController
. This causes the collection view to expand to the maximum height and width when rotated.
Once again, build, run and rotate; the rotation works as you would expect.
Now pick any rage face and click on it. Crash again!
You will see something like this in the console:
2013-08-02 21:52:46.127 RWRageFaces[1126:c07] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named UIStoryboardEmbedSegueTemplate' *** First throw call stack: (0x14a0022 0x1195cd6 0x1448a48 0x14489b9 0x6194a3 0x61967b 0x619383 0x3bdf99 0x51a135 0x619c6e 0x619383 0x519cad 0x619c6e 0x61967b 0x619383 0x519105 0x722eef 0x723477 0x3c05ab 0x33b7 0xfb94 0xf73d 0xef01 0x14a1e99 0x3e5c49 0x3e5cb6 0x14a1e99 0x3e5c49 0x3e5cb6 0x5bca1a 0x147499e 0x140b640 0x13d74c6 0x13d6d84 0x13d6c9b 0x22ab7d8 0x22ab88a 0x2f8626 0x242d 0x2355) terminate called throwing an exception
This means an exception was thrown. You’re going to need to turn on an exception breakpoint to see what the problem actually is. To do this, go to the breakpoint navigator – the 6th tab in the left panel in Xcode and click the + button in the bottom left. Then click Add Exception Breakpoint…. Then click Done in the popup that appears.
Build & run the app again and do the same thing as before – click on any rage face. Now you’ll see that it’s throwing an exception in the following line:
[self performSegueWithIdentifier:@"toDetailViewController" sender:indexPath];
It seems like there’s a problem with -performSegueWithIdentifier:
. Navigate back to the main storyboard and check out RWDetailViewController
: it contains a UIPageViewController
via an embed segue.
Embed segues make it easy to manage the containment of view controllers using Interface Builder, but they weren’t introduced until iOS 6. Your iOS 5 target won’t support them.