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
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Availability for Cross-Platform Development

As if availability attributes weren’t cool enough already, what if I told you that they could make it much easier to reuse your code on multiple platforms?

Availability attributes let you specify the platforms you want to support along with the versions of those platforms you want to use. To demonstrate this, you’re going to port Persona to macOS.

First things first: you have to set up this new macOS target. Select File/New/Target, and under macOS choose Application/Cocoa Application.

New macOS target

Set the Product Name to Persona-macOS, make sure Language is set to Swift and Use Storyboards is selected. Click Finish.

Just like you added support for iOS 8.4, you need to support older versions of macOS as well. Select the macOS target and change the Deployment Target to 10.10.

Set the deployment target to 10.10

Next, delete AppDelegate.swift, ViewController.swift, and Main.storyboard in the macOS target. In order to avoid some boilerplate work, download these replacement files and drag them into the project.

Note: When adding these files to the Xcode project, make sure the files are added to the Persona-macOS target, not the iOS target.

Drag in the new files

If Xcode asks if you want to set up an Objective C Bridging Header, click Don’t create.

So far, this target has the image view and two labels set up similar to the Persona iOS app, with a button to get a new random person.

One problem right now is that the images and vCards only belong to the iOS target — which means that your macOS target does not have access to them. This can be fixed easily.

In the Project navigator, select all the files in the Images and vCards folder. Open the File Inspector in the Utilities menu, and under Target Membership, check the box next to Persona-macOS:

Add vCards to the macOS target

You need to repeat this step again for PersonPopulator.swift, since your macOS app needs this file as well.

Now that you’ve completed the setup, you can start digging into the code.

Multi-Platform Attributes

Open PersonPopulator.swift. You may notice that the attributes all specify iOS, but there’s nothing about macOS — yet.

iOS 9.0 was released alongside with OS X version 10.11, which means that the new Contacts framework was also introduced on OS X in 10.11.

In PersonPopulator above generateContactInfo(), replace @available(iOS 9.0, *) with the following:

@available(iOS 9.0, OSX 10.11, *)

This specifies that generateContactInfo() is first available on OS X 10.11, to match the introduction of the Contacts framework.

Note: Because OS X was renamed macOS in macOS 10.12 Sierra, Swift recently added macOS as an alias for OSX. As a result, both OSX and macOS can be used interchangeably.

Next, you need to change the availability of generateRecordInfo() so it also works on macOS.

In the previous change, you combined iOS and OS X in a single attribute. However, that can only be done in attributes using that shorthand syntax; for any other @available attribute, you need to add multiple attributes for different platforms.

Directly after the deprecation attribute, add the following:

@available(OSX, deprecated:10.11, message:"Use generateContactInfo()")

This is the same thing as the line above it, but specifies for OS X instead of iOS.

Switch to the Persona-macOS target scheme, select My Mac as the build device, and build the project. There is one error in generateRecordInfo(), at the following code block:

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

The Contacts framework is a little different between iOS and macOS, which is why this error popped up. To fix this, you want to execute different code on iOS and macOS. This can be done using a preprocessor command.

Replace the previous code with the following:

#if os(iOS)
  let person = ABPersonCreate().takeRetainedValue()
  let people = ABPersonCreatePeopleInSourceWithVCardRepresentation(person, data).takeRetainedValue()
  let record = NSArray(array: people)[0] as ABRecord
#elseif os(OSX)
  let person = ABPersonCreateWithVCardRepresentation(data).takeRetainedValue() as AnyObject
  guard let record = person as? ABRecord else {
    fatalError()
  }
#else
  fatalError()
#endif

This makes the code work the same way on both platforms.

Linking Up the UI

Now that you finished updating PersonPopulator, setting up ViewController will be a breeze.

Open Persona-macOS’s ViewController.swift and add the following line to awakeFromNib():

getNewData(nil)

Next, add the following to getNewData(_:):

let firstName: String, lastName: String, title: String, profileImage: NSImage

if #available(OSX 10.11, *) {
  let (contact, imageData) = PersonPopulator.generateContactInfo()
  firstName = contact.givenName
  lastName = contact.familyName
  title = contact.jobTitle
  profileImage = NSImage(data: imageData)!
} else {
  let (record, imageData) = PersonPopulator.generateRecordInfo()
  firstName = record.value(forProperty: kABFirstNameProperty) as! String
  lastName = record.value(forProperty: kABLastNameProperty) as! String
  title = record.value(forProperty: kABTitleProperty) as! String
  profileImage = NSImage(data: imageData)!
}
    
profileImageView.image = profileImage
titleField.stringValue = title
nameField.stringValue = "\(firstName) \(lastName)"

Other than some small differences between iOS and macOS APIs, this code looks very familiar.

Now it’s time to test the macOS app. Change the target to Persona-macOS and select My Mac as the build device. Run the app to make sure it works properly.

Sir Isaac Newton is impressed.

Newton seems impressed! By making just those small changes to PersonPopulator, you were able to easily port your iOS app to another platform.

More Info About @available

Availability attributes can be a little confusing to format and to use. This section should help clear up any questions you may have about them.

These attributes may be placed directly above any declaration in your code, other than a stored variable. This means that all of the following can be preceded by an attribute:

  • Classes
  • Structs
  • Enums
  • Enum cases
  • Methods
  • Functions

To indicate the first version of an operating system that a declaration is available, use the following code:

@available(iOS, introduced: 9.0)

The shorthand, and preferred syntax, for marking the first version available is shown below:

@available(iOS 9.0, *)

This shorthand syntax allows you to include multiple “introduced” attributes in a single attribute:

@available(iOS, introduced: 9.0)
@available(OSX, introduced: 10.11)
// is replaced by
@available(iOS 9.0, OSX 10.11, *)

Other attributes specify that a certain declaration no longer works:

@available(watchOS, unavailable)
@available(watchOS, deprecated: 3.0)
@available(watchOS, obsoleted: 3.0)

These arguments act in similar ways. unavailable signifies that the declaration is not available on any version of the specified platform, while deprecated and obsoleted mean that the declaration is only relevant on older platforms.

These arguments also let you provide a message to show when the wrong declaration is used, as you used before with the following line:

@available(OSX, deprecated:10.11, message: "Use generateContactInfo()")

You can also combine a renamed argument with an unavailable argument that helps Xcode provide autocomplete support when used incorrectly.

@available(iOS, unavailable, renamed: "NewName")

Finally, the following is a list of the platforms you can specify availability for:

  • iOS
  • OSX
  • tvOS
  • watchOS
  • iOSApplicationExtension
  • OSXApplicationExtension
  • tvOSApplicationExtension
  • watchOSApplicationExtension

The platforms that end with ApplicationExtension are extensions like custom keyboards, Notification Center widgets, and document providers.

Note: The asterisk in the shorthand syntax tells the compiler that the declaration is available on the minimum deployment target on any other platform.

For example, @available(iOS 9.0, *) states that the declaration is available on iOS 9.0 or greater, as well as on the deployment target of any other platform you support in the project.

On the other hand, @available(*, unavailable) states that the declaration is unavailable on every platform supported in your project.

Evan Dekhayser

Contributors

Evan Dekhayser

Author

Over 300 content creators. Join our team.