What is the Swift preprocessor equivalent to iOS version check comparison?
Asked Answered
M

6

27

I am getting

yld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

and here's the function that is causing the error when the application is running on an iOS7 device and without even calling the function at all in the code.

func reigsterForRemoteUserNotifications(notificationTypes: UIUserNotificationType, categories: NSSet) {
        let userNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: categories)
        (UIApplication.sharedApplication()).registerUserNotificationSettings(userNotificationSettings)
        UIApplication.sharedApplication().registerForRemoteNotifications()
    }

I don't want this method to be accessible at all when running on an iOS7 device. I do not want a select check inside of it because that means the method is available for use to begin with.

What I want is a build config parameter to check the version : I can't figure out a way to write a swift equivalent preprocessor macro to check for the correct iOS version and neglect the new and undeclared iOS 8 library functions.

#if giOS8OrGreater
// declare the functions that are iOS 8 specific
#else 
// declare the functions that are iOS 7 specific

#endif

In the documentation apple is suggesting functions and generics to substitute for complex macros but in this case I need a build config precompile check to avoid processing undeclared functions. Any suggestions.

Melly answered 11/6, 2014 at 15:30 Comment(9)
Is there any reason why you wouldn't check that at runtime? In fact, I am not sure if you can check that at compile time since otherwise it would only run either on iOS8 and greater OR iOS7 and less.Danny
You shouldn't be using version comparisons to check for optional functionality in Objective-C (a variety of schemes, including checking for nil and respondsToSelector should be used) or in swift where you should be using optional checks and optional chaining instead.Cutshall
Try watching the Advanced Interoperability video from WWDC, I haven't had a chance yet, but I'm sure something's in there.Cutshall
Check out this recent post from NSHipster:nshipster.com/ios8 Especially the section on checking version!Gargoyle
NSHipster's post on NSProcessInfo().isOperatingSystemAtLeastVersion(yosemite) seems to be checking whether OS X is Yosemite or not. Not sure if it would apply to iOS.Lampley
The analog of your posted code doesn't do what you think it does in Objective-C either. You're combining runtime time code let giOS8OrGreater... with a compile time check #if giOS8OrGreater This combination wouldn't have worked in Objective-C either. The preprocessor would look for a giOS8OrGreater preprocessor symbol, and not finding one, the #if will always fail. If you had the right side of the let statement as a #define, you'd just get a compile time error because the preprocessor has very limited expression evaluation capability.Cutshall
You can do: var systemVersion = (UIDevice.currentDevice().systemVersion as NSString).floatValue but it will not work on iOS 7 anyway because if the compiled code has any references to new framework APIs it will crash with Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings Bottom line is you cannot check it in runtime //cc @DannyPendleton
This seems to be fixed in Xcode 6 beta 6Disorganization
A detailed post by the one and only matt: nshipster.com/swift-system-version-checkingMelly
D
27

The other answers fail to mention proper ways of checking the system version. You should absolutely never utilize: Device.systemVersion You shouldn't make custom macros to check version numbers, and you shouldn't dig beyond the libraries that Apple has specifically defined for this task.

There's a great article detailing this out here.

Note that Swift 2.0 allows you to directly check if an OS version number is available via:

if #available(iOS 10.0, *) {
    // modern code
} else {
    // Fallback on earlier versions
}

Prior to Swift 2.0, the recommended approach was via the system macros provided:

    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_9_0) {
    // do stuff for iOS 9 and newer
} else {
    // do stuff for older versions than iOS 9
}

or via:

    if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 10, minorVersion: 0, patchVersion: 0)) {
    // modern code
}

For anything missing beyond the system macros.

Any other approach has been downplayed as unreliable and not recommended by Apple. There's actually an approach that will break in iOS 10.

Note that if you need macro like functionality in a check and you'd like to use #available you can use @available defined in this article as such:

 @available(iOS 7, *)
func iOS7Work() {
    // do stuff

    if #available(iOS 8, *) {
        iOS8Work()
    }
}

@available(iOS 8, *)
func iOS8Work() {
    // do stuff
    if #available(iOS 9, *) {
        iOS9Work()
    }
}

@available(iOS 9, *)
func iOS9Work() {
    // do stuff
}

For further information on attributes in Swift, you can reference Apple's documentation.

Duala answered 13/7, 2016 at 21:45 Comment(1)
Nice. I needed the @available-syntax to conform a class to the new Sign In With Apple-related protocols, which are only available from iOS 13, without dropping support for older OS versions. You can just mark a protocol-conforming extension with this tag, like @available(iOS 13, *) extension MyClass: ASAuthorizationControllerDelegate. Works perfectly.Fetching
C
12

In Constants.swift:

Swift 1:

let Device = UIDevice.currentDevice()
private let iosVersion = NSString(string: Device.systemVersion).doubleValue

Swift 2:

let Device = UIDevice.currentDevice()
private let iosVersion = Double(Device.systemVersion) ?? 0 

let iOS8 = iosVersion >= 8
let iOS7 = iosVersion >= 7 && iosVersion < 8

Then in other files

if iOS8
{

}
else
{

}
Classmate answered 9/8, 2014 at 15:14 Comment(2)
But it say error when I try to build for target iOS 7 if I call some methods only available on iOS8 (ex: SKShareNode(rect...))Windjammer
Please try swift 2 versionClassmate
D
4

Update: This is fixed in Xcode 6 beta 6 (build 6A280e)


Here's a (perhaps not so great) workaround: explicitly link to UIKit weakly (I know that import statements are supposed to link to frameworks already, but we do it explicitly anyway).

  • Click on the project in Xcode
  • select the target in the drop down at the top left
  • go to General tab
  • scroll down to Linked Frameworks and Libraries
  • add UIKit.framework
  • change Status from Required to Optional

It may cause all of UIKit to be weakly linked, which may affect performance. Not sure.

In Objective-C, even with Required, the compiler automatically weak-linked the symbols whose availability is above the deployment target, and leave the others strongly linked. I don't know why this doesn't work with Swift.

Disorganization answered 17/6, 2014 at 19:21 Comment(0)
S
4

var systemVersion = UIDevice.currentDevice().systemVersion

Subsistent answered 22/9, 2014 at 10:58 Comment(1)
It's horrible and not recommended. Reference my response.Duala
L
3

According to Apple, there are no preprocessor calls in Swift, but Swift code can be conditionally compiled based on the evaluation of build configurations in two functions. For now, we get os() for a choice of OSX or iOS and arch() for a choice of x86_64, arm, arm64 or i386. So, you can evaluate #if os(iOS), but not #if os(iOS8) (though that does seem like a good idea for the further versions of Swift).

Lampley answered 11/6, 2014 at 16:0 Comment(2)
That would have been nice but unfortunately that's not the caseMelly
You can use #if os(iOS) but it's not down the os version.Cutshall
H
1

You can check iOS version by -

let iosVersion = UIDevice.currentDevice().systemVersion

And compare it using NSStringCompareOptions.NumericSearch

switch iosVersion.compare("8.0.0", options: NSStringCompareOptions.NumericSearch)
{
    case .OrderedSame, .OrderedDescending:
        //iOS>=8.0.0
        return LAContext()
    case .OrderedAscending:
        //iOS<8.0.0
        return nil
}

You can also use the new NSProcessInfo APIs, but they're unavailable for iOS 7.

if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
//iOS>=8.0.0
}

You can see more details here. Hope this solves your query.

Hildegardhildegarde answered 26/5, 2015 at 5:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.