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.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 6 of this article. Click here to view the first page.

Working Around Embed Segues in iOS 5

Open MainStoryboard.storyboard.

Select the container view embedded inside Detail View Controller Scene (it says “Container” in the middle) and delete it. Next, select the dangling UIPageViewController and delete it. This automatically gets rid of the embed segue in Interface Builder.

While you’re here, you should also pin the navigation bar in the detail view controller to the top, just like you did in the grid view controller. This will ensure the navigation bar sticks to the top of the view.

Next, open up RWDetailViewController.m and delete prepareForSegue:, which was used to set up the child view controller using the embed segue. You’ll have to do view controller containment the old way!

Still in RWDetailViewController.m, replace viewDidLoad with the following:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RWRageFaceViewController *rageFaceViewController =
    [self.storyboard instantiateViewControllerWithIdentifier:@"RWRageFaceViewController"];
    
    rageFaceViewController.index = self.index;
    rageFaceViewController.imageName = self.imageNames[self.index];
    rageFaceViewController.categoryName = self.categoryName;
    
    //Initialize UIPageViewController programatically
    self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
                                                              navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                                                                            options:nil];
    
    CGFloat navBarHeight = self.navigationBar.frame.size.height;
    CGRect detailViewControllerFrame = CGRectMake(0, navBarHeight, self.view.frame.size.width,
                                                  self.view.frame.size.height - navBarHeight);
    self.pageViewController.view.frame = detailViewControllerFrame;
    self.pageViewController.view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
    
    self.pageViewController.delegate = self;
    self.pageViewController.dataSource = self;
    
    //Add UIPageViewController as a child view controller
    [self addChildViewController:self.pageViewController];
    [self.view addSubview:self.pageViewController.view];
    [self.pageViewController didMoveToParentViewController:self];
    
    [self.pageViewController setViewControllers:@[rageFaceViewController]
                                      direction:UIPageViewControllerNavigationDirectionForward
                                       animated:NO
                                     completion:nil];
    
}

In the code above, you initialize the UIPageViewController in code instead of initializing it automatically via the storyboard. You then set its frame, delegate and data-source and then add it as the child view controller of RWDetailViewController.

Build and run your app, and tap on any rage face. Xcode still crashes — but the crash originates from a different location: RWRageFaceViewController.m:

[attributedTitle addAttribute:NSForegroundColorAttributeName
                            value:[UIColor redColor]
                            range:categoryRange];

In the original iOS 6 app the label below the rage face image displays the rage face’s category name in red and the name of the image in black, using an NSAttributedString to do the job.

You can probably guess that NSAttributedString wasn’t introduced until…iOS 6!

Working Around NSAttributedString in iOS 5

The easiest way to fix this is to populate the UILabel using an NSAttributedString in iOS 6, and a regular NSString in iOS 5.

Open RWRageFaceViewController.m and replace viewDidLoad with the following:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.imageView.image = [UIImage imageNamed:self.imageName];
    NSString *titleString = [NSString stringWithFormat:@"%@: %@", self.categoryName, self.imageName];
    
    /* Use NSAttributedString with iOS 6 + */
    if ([self.imageLabel respondsToSelector:@selector(setAttributedText:)]) {
        
        NSMutableAttributedString *attributedTitle = [[NSMutableAttributedString alloc] initWithString:titleString];
        
        NSRange categoryRange = [titleString rangeOfString:[NSString stringWithFormat:@"%@: ", self.categoryName]];
        NSRange titleRange = [titleString rangeOfString:[NSString stringWithFormat:@"%@", self.imageName]];
        
        [attributedTitle addAttribute:NSForegroundColorAttributeName
                                value:[UIColor redColor]
                                range:categoryRange];
        
        [attributedTitle addAttribute:NSForegroundColorAttributeName
                                value:[UIColor blackColor]
                                range:titleRange];
        
        self.imageLabel.attributedText = attributedTitle;
    }
    
    /* Simple UILabel with iOS 5 */
    else {
        self.imageLabel.text = titleString;
    }
}

This has changed the method to first check for the existence of UILabel’s attributedText property, just like you saw earlier in the theory part of the tutorial. If the test passes, you know it’s safe to use an attributed string.

Build & run the project one more time, and tap on any rage face. Success — the app doesn’t crash! Now, simply swipe left or right to change screens and…wait, what happens?

RWRageFaces 18

Even though you initialized the UIPageViewController using transition style UIPageViewControllerTransitionStyleScroll, the app is behaving as if the page view controller had been initialized using the page curl transition style. What’s going on?

This behavior occurs because UIPageViewControllerTransitionStyleScroll is an enum value that was introduced in iOS 6. In iOS 5, the page view controller doesn’t know what to do with the scrolling transition style so it defaults to the page curl transition style.

As discussed before, there’s no way to check against the existence of an enum value at runtime because it evaluates to an integer. It would be like asking the question, “Does the integer 1 exist?”.

Working Around Page Transitions in iOS 5

To get around this problem you’ll have to check the availability of the constant UIPageViewControllerOptionInterPageSpacingKey, which was also introduced in iOS 6.

In RWDetailViewController.m, change viewDidLoad to match the following implementation:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RWRageFaceViewController *rageFaceViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"RWRageFaceViewController"];
    
    rageFaceViewController.index = self.index;
    rageFaceViewController.imageName = self.imageNames[self.index];
    rageFaceViewController.categoryName = self.categoryName;
    
    CGFloat navBarHeight = self.navigationBar.frame.size.height;
    CGRect detailViewControllerFrame = CGRectMake(0, navBarHeight, self.view.frame.size.width,
                                                  self.view.frame.size.height - navBarHeight);
    
    /* Use UIPageViewController in iOS 6+ */
    
    //1
    if (&UIPageViewControllerOptionInterPageSpacingKey) {
        
        //2
        self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
                                                                  navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                                                                                options:@{UIPageViewControllerOptionInterPageSpacingKey: @(35)}];
        
        self.pageViewController.delegate = self;
        self.pageViewController.dataSource = self;
        
        self.pageViewController.view.frame = detailViewControllerFrame;
        self.pageViewController.view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
        [self.view addSubview:self.pageViewController.view];
        
        [self.pageViewController setViewControllers:@[rageFaceViewController]
                                          direction:UIPageViewControllerNavigationDirectionForward
                                           animated:NO
                                         completion:nil];
    }
    
    //3
    else {
        
        [self addChildViewController:rageFaceViewController];
        rageFaceViewController.view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
        rageFaceViewController.view.frame = detailViewControllerFrame;
        [self.view addSubview:rageFaceViewController.view];
        [rageFaceViewController didMoveToParentViewController:self];
    }
}

Here’s what’s going on in the above code, step by step:

  1. Check for the string constant UIPageViewControllerOptionInterPageSpacingKey using the & operator. If the memory address exists, that the constant exists as well and you must be running on iOS 6.
  2. Since you’ve discovered that UIPageViewControllerOptionInterPageSpacingKey exists, you might as well use it to add a 35 point margin between the rage face view controllers.
  3. If UIPageViewControllerOptionInterPageSpacingKey is not available, it means your app is running on an iOS 5.X device or below. Since the scrolling transition style is not available, forgo the page view controller entirely and simply show one rage face at a time.

Build and run again and tap on any rage face. Notice that you can’t scroll left or right anymore; that’s the intended behavior in iOS 5 so you can move on.

Note: Depending on your needs, it’s perfectly fine to drop a feature in an older version of iOS and focus your efforts elsewhere. If you really need to support the scrolling transition style in iOS 5, you’re faced with finding an open source solution to fit your needs or implement your own UIPageViewController. Both options can be very time consuming, so make sure it’s worth the effort before going down that road!

While RWDetailViewController is up, tap on the Share button on the top right corner to reveal an activity sheet inside a popover.

Tapping on either of the Twitter or Facebook options crashes the app in iOS 5. For example, tapping on Facebook crashes in the following place:

if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {

Sharing via Twitter or Facebook uses SLComposeViewController which — you guessed it — didn’t exist in iOS 5.

The easiest way to fix something that’s broken is to get rid of it altogether. Draconian? Yes. Effective? Totally. :]

Contributors

Over 300 content creators. Join our team.