12

I would like to detect if the user has enabled Reduce Transparency. It's simple you just call the func UIAccessibilityIsReduceMotionEnabled() and it returns a Bool. But my app targets iOS 7 and 8 and this function isn't available on iOS 7.

In Objective-C, this is how I checked to see if that function exists:

if (UIAccessibilityIsReduceMotionEnabled != NULL) { }

In Swift, I can't figure out how to check if it exists or not. According to this answer, you can simply use optional chaining and if it's nil then it doesn't exist, but that is restricted to Obj-C protocols apparently. Xcode 6.1 doesn't like this:

let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()

It wants you to remove the ?. And of course if you do so it will crash on iOS 7 because that function doesn't exist.

What is the proper way to check if these types of functions exist?

Community
  • 1
  • 1
Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • Parentheses after a function expression invoke the function. What happens if you leave them off? – outis Nov 03 '14 at 02:33
  • If you leave off () it says the same thing - delete the ?: `Operand of postfix '?' should have optional type; type is '() -> Bool'`. Also if you move the ? after the (), it's all the same error message. – Jordan H Nov 03 '14 at 02:35
  • I suspect it is indeed not available yet. From my understanding, optional chaining is used for class variables. `UIAccessibilityIsReduceMotionEnabled` is more like static function – Peter Nov 03 '14 at 02:49
  • What happens if you assign the function to a variable with an optional closure type? `let reduceMotionDetectionIsAvailable : (() -> Bool)? = UIAccessibilityIsReduceMotionEnabled` – outis Nov 03 '14 at 06:08

4 Answers4

10

A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.

var shouldApplyMotionEffects = true
if #available(iOS 8.0, *) {
    shouldApplyMotionEffects = !UIAccessibilityIsReduceMotionEnabled()
}
Jordan H
  • 52,571
  • 37
  • 201
  • 351
6

If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:

let uikitbundle = NSBundle(forClass: UIView.self)
let uikit = dlopen(uikitbundle.executablePath!, RTLD_LAZY)
let handle = dlsym(uikit, "UIAccessibilityIsReduceMotionEnabled")
if handle == nil {
    println("Not available!")
} else {
    println("Available!")
}

The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.

As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.

aethe
  • 909
  • 1
  • 8
  • 12
  • cheeky as in get yourself into trouble when Apple's underlying implementation changes without warning. `dlopen()` is getting too far under the hood to be practical for a serious app on the app store to gamble on. – clearlight Apr 05 '23 at 22:23
3

You could check to see if you're running in iOS 8 or higher --

var reduceMotionEnabled = false
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
    reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}

I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:

let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled
// reduceMotionDetectionIsAvailable is now a () -> Bool
reduceMotionDetectionIsAvailable()
// crashes in iOS7, fine in iOS8

The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:

// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
    return (UIAccessibilityIsReduceMotionEnabled != NULL);
}

// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
    reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • Yeah that's one way to do it. Apple would recommend against that though and has in the past during WWDC sessions. There's many times you need to detect if a function exists, especially when supporting multiple OSes. Is this something that doesn't exist in Swift? That sounds crazy. – Jordan H Nov 03 '14 at 02:38
  • 1
    Thanks, goodness that's terrible. :P Time to file an enhancement request! I won't be using Obj-C, this app is pure Swift. – Jordan H Nov 03 '14 at 02:59
0

From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):

Swift code can use the availability of APIs as a condition at run-time. Availability checks can be used in place of a condition in a control flow statement, such as an if, guard, or while statement.

Taking the previous example, you can check availability in an if statement to call requestWhenInUseAuthorization() only if the method is available at runtime:

let locationManager = CLLocationManager()
if #available(iOS 8.0, macOS 10.10, *) {
    locationManager.requestWhenInUseAuthorization()
}

Alternatively, you can check availability in a guard statement, which exits out of scope unless the current target satisfies the specified requirements. This approach simplifies the logic of handling different platform capabilities.

let locationManager = CLLocationManager()
guard #available(iOS 8.0, macOS 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()

Each platform argument consists of one of platform names listed below, followed by corresponding version number. The last argument is an asterisk (*), which is used to handle potential future platforms.

Platform Names:

  • iOS
  • iOSApplicationExtension
  • macOS
  • macOSApplicationExtension
  • watchOS
  • watchOSApplicationExtension
  • tvOS
  • tvOSApplicationExtension
Benjamin Cheah
  • 1,401
  • 17
  • 23