@available attribute in Swift

Oct 5, 2023

Quick overview of how to use them

It’s always the same. A new iOS release means there are new APIs available to use in your project. But sometimes you do not have the option to raise the minimum supported iOS version, because this would reduce your user base, even if 50% of the users have already installed the new version just a month later.

📈 iOS 16 adoption report

Image.png

Statistic from Mixpanel

But you don’t have to wait for such new features to be developed and released until you drop older versions of iOS. The #available attribute helps us to add conditional checks for different iOS versions, and @available allows us to mark certain classes or methods as only available for certain versions or platforms.

@available to mark classes and methods

@available(platform-name version-number, *)
@available(swift version-number)

Use this annotation to make classes or methods only available for specific platforms.

@available(iOS 16.0, macOS 12.0, *)
struct DataTableView: View {
  let data: [DataModel]

  var body: some View {
    // available of Table
    // @available(iOS 16.0, macOS 12.0, *)
    // @available(tvOS, unavailable)
    // @available(watchOS, unavailable)
    Table(data) {
      ...
    }
  }
}

‌and the usage of conditional checks with #available helps us to use the new type in our code. Or with a fallback solution for older operating systems.

struct AppView: View { 
  @State var data: [DataModel] = [...] 
    
  var body: some View {         
    if #available(iOS 16, macOS 12.0, *) { 
      DataTableView(data: data) 
    } else { 
      DataStackView(data: data) 
    }

    if #unavailable(iOS 16) { 
      // only visible on iOS 15 and lower 
      Text("Please upgrade to iOS 16 to have the newest features available.") 
    } 
  }
}

#unavailable

If you want to show users with old iOS versions a hint, that some features are not available, you can use the #unavailable attribute.

if #unavailable(iOS 16) { 
  // only visible on iOS 15 and lower 
  Text("Please upgrade to iOS 16 to receive new updates") 
}

deprecation of classes and methods

@available(*, unavailable, message: "Networking is no longer needed since iOS 16") 
class Networking {}

With the unavailable argument we can force developers to not use a class anymore. This will result in a compile error.

@available(iOS, deprecated: 16, obsoleted: 16.1, renamed: "DataTableView") 
struct DataStackView: View { ... }

‌With the example above we can mark the DataStackView as deprecated in iOS 16, which will throw a warning, and make it obsoleted with iOS  16.1. The rename argument gives other developers a hint of which view should be used instead (our new DataTableView is available in iOS 16) and Xcode can fix it for them.

available platform values

The available attribute can not only limit the operating system version, you can also declare a specific swift version.

  • iOS
  • iOSApplicationExtension
  • macOS
  • macOSApplicationExtension
  • macCatalyst
  • macCatalystApplicationExtension
  • watchOS
  • watchOSApplicationExtension
  • tvOS
  • tvOSApplicationExtension
  • swift

You can also use the asterisk (*) already seen above to indicate the availability of the declaration on all the platform names listed above.

@available(*, unavailable, message: "No longer needed")

But how can we add a property which type is only avaiable on a later release into a class which can not easily have an available attribute?

Let me show you an example. We have a ViewController that is used in many places in the app. This controller should get a new iOS 17-only feature NewFeature, but how can we add it to a class that needs to support earlier versions?

@available(iOS 17, *)
class NewFeature {
  func newStuff() { /* calling iOS 17 only APIs */ }
}

// Deployment Target iOS 15
class ViewController: UIViewController {
  // How can we use a property which is only available in iOS 17?
}

Protocol and optional feature

One solution I used is to create a protocol with the public APIs of the feature and have it as an optional lazy property inside the ViewController. NewFeature only needs to validate against this protocol.

protocol NewFeatureProtocol {
  func newStuff()
}

@available(iOS 17, *)
extension NewFeature: NewFeatureProtocol {}

class ViewController: UIViewController {
  // NewFeatureProtocol is available
  lazy var newFeature: NewFeatureProtocol? = {
    if #available(iOS 17, *) {
      return NewFeature()
    } else {
      return nil
    }
  }()
}

We even have the possibility to implement a fallback solution which is available prior iOS 17.

Summary

In this article we have discussed the use of the @available and #available attributes to handle different iOS versions and platform-specific features.

Overall, these Swift attributes and conditional checks allow you to efficiently manage the availability of code features across different iOS versions and platforms efficiently.

Protocols can help to use these features as properties within other classes that cannot be increased in their available state.

Code snippets

if #available(iOS 16, *) { 
  print("iOS 16 and later")
} else { 
  print("iOS 15 or lower") 
}
guard #available(iOS 16, *) else { return } 
print("iOS 16 and later")
if #unavailable(iOS 16) { 
    // only visible on iOS 15 and lower 
    Text("Please upgrade to iOS 16 to receive new updates") 
}
@available(*, unavailable, message: "Class unavailable")
@available(iOS, deprecated: 16, obsoleted: 16.1, renamed: "New method name")


Made with 💜 by Michael Freiwald

Created with Ignite Swift Logo

Impressum