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.
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
Availability Attributes in Swift
15 mins
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.
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.
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.
Note: When adding these files to the Xcode project, make sure the files are added to the Persona-macOS target, not the iOS target.
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:
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.
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.
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.
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.