Use New Awesome iOS Features While Maintaining Backward Compatibility

Every year at WWDC Apple blesses us with plenty of new awesome tools that we can’t wait to implement in our apps.

hello-wwdc

However, most of the time, our apps must support previous iOS versions, too.

This might seem a big show-stopper and, in fact, there are many developers that are holding on in adding the latest new features because of this.

Well, I’m here to tell you that, thanks to Swift, it is incredibly clean and easy to add new iOS features, while maintaining backward compatibility!

Example

Let’s use a real example: one of my new favorite toys from this year WWDC is UIPreviewInteraction.

Basically, this (iOS 10.0+) class listens to the very same touch interactions of Peek and Pop for your UIView, but gives you complete control on what to do with it.

To implement it, we must follow just two steps:

  1. Add a UIPreviewInteraction property to our UIView
  2. Create a UIPreviewInteractionDelegate

1. Adding a UIPreviewInteraction Property to our UIView

Obviously, if we add the property as it is, the compiler will complain:

class MyView: UIView {
  fileprivate var myPreviewInteraction: UIPreviewInteraction
}
UIPreviewInteraction-unavailable

How do we overcome this? Easy: we don’t tell the compiler what the property is.

Furthermore, since we use the property only in iOS 10 and above, we will make it an optional, so the property will be nil for previous iOS versions:

class MyView: UIView {
  fileprivate var myPreviewInteraction: Any? = nil
}

Ok, now we can compile! 🎉 However, at some point, we must declare that the property is a UIPreviewInteraction.

How?

Simple, we use one of the most precious Swift 2 gifts: #available, a.k.a. Swift’s Automatic Operating System Availability Checking API:

class MyView: UIView {
  fileprivate var myPreviewInteraction: Any? = nil
  
  init() {
    super.init(frame: CGRect.zero)
    if #available(iOS 10.0, *) {
      myPreviewInteraction = UIPreviewInteraction(view: self)
    }
  }
}

Great! Now our property will be:

  • nil in any iOS prior to 10;
  • an instance of UIPreviewInteraction for iOS 10.0+!

2. Create a UIPreviewInteractionDelegate

For simplicity’s sake, I will use the same UIView class as my delegate as well.

One more time, if we simply try to make our UIView class conform to the UIPreviewInteractionDelegate, the compiler will complain:

extension MyView: UIPreviewInteractionDelegate {
  public func previewInteraction(_ previewInteraction: UIPreviewInteraction, didUpdatePreviewTransition transitionProgress: CGFloat, ended: Bool) { }
  public func previewInteractionDidCancel(_ previewInteraction: UIPreviewInteraction) { }
}
UIPreviewInteraction-unavailable-2

And, one more time, Swift 2’s Automatic Operating System Availability Checking API comes to the rescue!

@available(iOS 10.0, *)
extension MyView: UIPreviewInteractionDelegate {
  public func previewInteraction(_ previewInteraction: UIPreviewInteraction, didUpdatePreviewTransition transitionProgress: CGFloat, ended: Bool) { }
  public func previewInteractionDidCancel(_ previewInteraction: UIPreviewInteraction) { }
}

Now, let’s not forget to add the view as its own delegate in our initializer…and we’re done!

class MyView: UIView {
  fileprivate var myPreviewInteraction: Any? = nil
  
  init() {
    super.init(frame: CGRect.zero)
    if #available(iOS 10.0, *) {
      myPreviewInteraction = UIPreviewInteraction(view: self
      (myPreviewInteraction as! UIPreviewInteraction).delegate = self
    }
  }
}

Conclusions

Even though our apps has to support older iOS versions, this must not stop us to implement the latest and greatest iOS features!

It’s true, this solution won’t add the same functionality to previous iOS releases, but it’s still way better than waiting years to drop support of older iOS versions to finally implement what’s available to us today.

Code Snippet

Here's the final, complete, code snippet:

import UIKit

class MyView: UIView {
  fileprivate var myPreviewInteraction: Any? = nil
  
  init() {
    super.init(frame: CGRect.zero)
    
    if #available(iOS 10.0, *) {
      myPreviewInteraction = UIPreviewInteraction(view: self)
      (myPreviewInteraction as! UIPreviewInteraction).delegate = self
    }
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

@available(iOS 10.0, *)
extension MyView: UIPreviewInteractionDelegate {
  public func previewInteraction(_ previewInteraction: UIPreviewInteraction, didUpdatePreviewTransition transitionProgress: CGFloat, ended: Bool) { }
  public func previewInteractionDidCancel(_ previewInteraction: UIPreviewInteraction) { }
}
⭑⭑⭑⭑⭑