Availability Attributes in Swift

Ever wonder how to support multiple versions of iOS or cross-platform code? Swift availability attributes makes this messy task much cleaner. Learn all about them in this tutorial. By Evan Dekhayser.

Leave a rating/review
Save for later
Share

Although most users adopt new versions of iOS quite quickly, it is still important to make sure your apps work on previous versions. Apple recommends supporting one system version back, meaning that when iOS 10 is released this fall, you should still support iOS 9.3.

But what if you want to add a feature that is only available in the newest version, or you need to use a deprecated API on older versions?

That is where Swift availability attributes come in. These attributes make it easy for you to ensure your code works on every system version you support.

The best way to understand availability is to get your hands dirty with some code. Let’s dive in!

Note: You will need Xcode 8 or above to work through this tutorial.

Getting Started

Download the starter project and open Persona.xcodeproj. Persona is an app that shows random famous people from history, from ancient emperors to musical artists.

Persona is built using the Contacts framework, which was first introduced in iOS 9. This means that Persona only works on iOS 9 or greater. Your task is to add support for iOS 8.4. Talk about #throwbackthursday! :]

First, examine the project. In the Project navigator, there are two groups, vCards and Images. For each person in the app, there is a .vcf and .jpg image to match.

Open PersonPopulator.swift. PersonPopulator has a class method generateContactInfo(), which chooses a random person and returns the contact and image data.

Next, open the Persona group and move to ViewController.swift. Each time the user taps the “Random” button, getNewData() is called and repopulates the data with the new person.

Supporting iOS 8.4 with Availability Attributes

This app currently supports only iOS 9.3 and above. However, supporting iOS 8.4, the most recent version of iOS 8, would be ideal.

Setting the oldest compatible version of iOS is simple. Go to the General pane in Persona’s iOS target settings. Find the Deployment Info section and set Deployment Target to 8.4:

Set the iOS deployment target to 8.4

After making this change, build the project; it looks like things are broken already.

Xcode shows two errors in PersonPopulator:

Contacts was not introduced until iOS 9

In order to fix this error, you need to restrict generateContactInfo() to certain iOS versions — specifically, iOS 9 and greater.

Adding Attributes

Open PersonPopulator.swift and add the following attribute right above generateContactInfo():

@available(iOS 9.0, *)

This attribute specifies that generateContactInfo() is only available in iOS 9 and greater.

Checking the Current Version

Now that you’ve made this change, build the project and notice the new error in ViewController.swift.

The new error states that generateContactInfo() is only available on iOS 9.0 or newer, which makes sense because you just specified this condition.

To fix this error, you need to tell the Swift compiler that this method will only be called in iOS 9 and above. You do this using availability conditions.

Open ViewController.swift and replace the contents of getNewData() with the following:

if #available(iOS 9.0, *) {
  print("iOS 9.0 and greater")

  let (contact, imageData) = PersonPopulator.generateContactInfo()
  profileImageView.image = UIImage(data: imageData)
  titleLabel.text = contact.jobTitle
  nameLabel.text = "\(contact.givenName) \(contact.familyName)"
} else {
  print("iOS 8.4")
}

#available(iOS 9.0, *) is the availability condition evaluated at compile time to ensure the code that follows can run on this iOS version.

The else block is where you must write fallback code to run on older versions. In this case, the else block will execute when the device is running iOS 8.4.

Build and run this code on the iPhone simulator running iOS 9.0 or greater. Each time you click “Random”, you’ll see iOS 9.0 and greater printed to the console:

The availability switch causes iOS 9.0 to be printed

Adding Fallback Code

The Contacts framework introduced in iOS 9 replaced the older Address Book framework. This means that for iOS 8.4, you need to fall back to the Address Book to handle the contact information.

Open PersonPopulator.swift and add the following line to the top of the file:

import AddressBook

Next, add the following method to PersonPopulator:

class func generateRecordInfo() -> (record: ABRecord, imageData: Data) {
  let randomName = names[Int(arc4random_uniform(UInt32(names.count)))]

  guard let path = Bundle.main.path(forResource: randomName, ofType: "vcf") else { fatalError() }
  guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData else { fatalError() }

  let person = ABPersonCreate().takeRetainedValue()
  let people = ABPersonCreatePeopleInSourceWithVCardRepresentation(person, data).takeRetainedValue()
  let record = NSArray(array: people)[0] as ABRecord

  guard let imagePath = Bundle.main.path(forResource: randomName, ofType: "jpg"),
        let imageData = try? Data(contentsOf: URL(fileURLWithPath: imagePath)) else {
      fatalError()
  }
  return (record, imageData)
}

This code does the same thing as generateContactInfo(), but using the Address Book instead. As a result, it returns an ABRecord instead of a CNContact.

Because the Address Book was deprecated in iOS 9, you need to mark this method as deprecated as well.

Add the following attribute directly above generateRecordInfo():

@available(iOS, deprecated:9.0, message:"Use generateContactInfo()")

This attribute lets the compiler know that this code is deprecated in iOS 9.0, and provides a message to warn you or another developer if you try to use the method in iOS 9 or greater.

Now it’s time to use this method.

Open ViewController.swift and add the following import statement to the top of the file:

import AddressBook

Also, add the following to the else block in getNewData():

print("iOS 8.4")

let (record, imageData) = PersonPopulator.generateRecordInfo()
      
let firstName = ABRecordCopyValue(record, kABPersonFirstNameProperty).takeRetainedValue() as! String
let lastName = ABRecordCopyValue(record, kABPersonLastNameProperty).takeRetainedValue() as! String
      
profileImageView.image = UIImage(data: imageData)
titleLabel.text = ABRecordCopyValue(record, kABPersonJobTitleProperty).takeRetainedValue() as? String
nameLabel.text = "\(firstName) \(lastName)"

This code gets the random record info and sets the labels and the image the same way you have it in generateContactInfo(). The only difference is instead of accessing a CNContact, you access an ABRecord.

Build and run the app on the simulator for iOS 9 or above, and everything will work as it did before. You will also notice that your app prints iOS 9.0 and greater to the console:

E=MC²

However, the goal of everything you have done so far is to make Persona work on iOS 8.4. To make sure that this all worked, you need to try it out in the iOS 8.4 Simulator.

Go to Xcode/Preferences/Components, and download the iOS 8.4 Simulator.

Download the iOS 8.4 simulator

When the simulator is finished downloading, select the iPhone 5 iOS 8.4 Simulator and click Run.

Select the iPhone 5 simulator

Persona runs the exact same way that it used to, but now it’s using the Address Book API. You can verify this in the console which says iOS 8.4, which is from your code in the else block of the availability conditional.

Evan Dekhayser

Contributors

Evan Dekhayser

Author

Over 300 content creators. Join our team.