iOS - #available does not guard availability here; use if (#available) instead - ios

I am using below code but getting the warning,
bool versionSupports = (#available(iOS 10, *));
#available does not guard availability here; use if (#available)
instead
There is a solution where I can use
if (#available(iOS 10, *)){
//compile
}else{
//fallback
}
I was curious, why an output is placeable inside if() condition but not placeable into a boolean variable?

#available(iOS 10, *) is not a BOOL expression. It does not return an indication of whether the code is being run on that version of iOS or not.
It needs to be in the form of:
if (#available(iOS 10, *)) {
// Code that requires iOS 10 or later
} else {
// Code for iOS 9 or earlier
}
If you want a variable that indicates whether the current device is running iOS 10 or later then you can do something like:
BOOL versionSupports = [UIDevice currentDevice].systemVersion.floatValue >= 10;
You may also find Objective-C #available guard AND'ed with more conditions helpful depending on your needs.

That says,
#available may only be used as condition of an 'if', 'guard' or 'while' statement
so, you can directly use that in if statements to check the availability instead of taking it into a variable.
if (#available(iOS 10, *)) {
// PUT YOUR CODE HERE
} else {
}
In Swift 4.1
if #available(iOS 10.0, *) {
} else {
}

A workaround can be:
BOOL isGoodIos = false;
if(#available(iOS 11, *)) { isGoodIos = true; }
....
BOOL shouldDoMagic = true;
if(shouldDoMagic && isGoodIos) {
// TODO: magic
}

Related

Swift iOS version availability - check from * to iOS 13

I have tried to use such check to apply some code that should works only on previous versions of iOS before iOS 13 and it doesn't work correctly i.e. it is executed on iOS 13
if #available(*, iOS 12) { }
I make a workaround like this
if #available(iOS 13, *) {
/// Do nothing here
} else {
}
But i have additional curly braces block
You can use guard statement
guard #available(iOS 13.0, *) else {
// Code for earlier iOS versions
return
}
You can get the currentVersion of your OS using:
UIDevice.current.systemVersion
Using this you can easily create your own method.
func SYSTEM_VERSION_LESS_THAN(version: String) -> Bool {
return UIDevice.current.systemVersion.compare(version,
options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
}
In Swift 5.6 (Xcode 13.3+), you can use #unavailable condition.
if #unavailable(iOS 13) {
// This code will run on iOS 12.* and earlier
}
This is the proposal for reference: SE-0290.

How can I create instance variable based on OS version

How can I define instance variable based on the iOS verison, for example
CNContactStore is available since iOS9.0 and ABAddressBook is deprecated in 9.0 but I want to create two variable depends upon iOS version. My Approach is
if #available(iOS 9.0, *) {
var addressBookRef: ABAddressBook? = nil
} else {
var contactStore : CNContactStore? = nil
}
If I do this inside a method body, works fine but I want it to be define globally and can be access throughout the class but giving me error if I do it like this
#available(iOS 9.0, *)
var contactStore : CNContactStore? = nil
Need some suggestions. How do I achieve this. I've no idea if I release #available(iOS 9.0, *), it will crash my app on iOS 8.x or below?
In that case I would use a wrapper class mimic even ABAddressBook and CNContactStore behaviour so all the call to their methods should be done through this class.
A quick approach for what you are looking could be like this:
class ContactWrapper {
var contactBook: NSObject?
init() {
if #available(iOS 9, *) {
contactBook = CNContactStore.init()
} else {
contactBook = ABAddressBookCreate().takeRetainedValue() as? NSObject
}
}
}

Extended property if not available

With Swift 2, Apple introduced the API availability checking which allows one to execute certain code only on a specified version or later, like this:
if #available(iOS 9, *) {
// use UIStackView
} else {
// use fallback
}
For instance, iOS 9.0 introduces the localizedUppercaseString property:
/// An uppercase version of the string that is produced using the current
/// locale.
public var localizedUppercaseString: String { get }
What I want is to create an exact replica of this property that is only available for versions lower than 9.0 so I do not have to check if #available(iOS 9, *) whenever I need to use this (or any other) property/method.
The best result I could get was the following:
extension String {
#available(iOS 8.0, *)
var localizedUppercaseString: String {
return uppercaseStringWithLocale(NSLocale.currentLocale())
}
}
With this, I can call localizedUppercaseString, no matter if the iOS version is 8.0 or 9.0. The problem is that this extension overrides the "original" property when executed with iOS 9.0.
extension String {
var myLocalizedUppercaseString: String {
if #available(iOS 9, *) {
return localizedUppercaseString
} else {
return uppercaseStringWithLocale(NSLocale.currentLocale())
}
}
}
Now you just have to use myLocalizedUppercaseString property.

Checking for quick actions availability

I'm using home screen quick actions that's only supported in IOS9.
Using the constant UIApplicationLaunchOptionsShortcutItemKey will crash if used in IOS8.
What is the correct way to check if quick actions is supported?
One way is to check for IOS9 through systemVersion but I'm hoping there is a better way.
[[UIDevice currentDevice] systemVersion]
In objective C you can check to see if a class exists. Say something like
if([UIApplicationShortcutItem class]){
//Handle shortcut launch
}
I think in case of Swift the best way for checking the API availability is Automatic operating system API availability checking that's new feature released with iOS9 and Swift2
if #available(iOS 9, *) {
// use UIApplicationLaunchOptionsShortcutItemKey
} else {
// no available
}
#available is going to check whether are we using iOS 9 or later, or any other unknown platforms like watchOS so the * is also here.
If your code is inside a function then you can use #available with guard like this.
guard #available(iOS 9, *) else {
return
}
Mark your methods and class as well like
#available(iOS 9, *)
func useMyStackView() {
// use UIStackView
}
#available works similarly to #available so If your deployment target is iOS7 or less than 9, you can't call that useMyStackView()
For Objc, it also can use #available (iOS 9, *) to check the OS version
if (#available(iOS 9, *)) {
//use UIApplicationLaunchOptionsShortcutItemKey
}

Check for class existence in Swift

I want to use NSURLQueryItem in my Swift iOS app. However, that class is only available since iOS 8, but my app should also run on iOS 7. How would I check for class existence in Swift?
In Objective-C you would do something like:
if ([NSURLQueryItem class]) {
// Use NSURLQueryItem class
} else {
// NSURLQueryItem is not available
}
Related to this question is: How do you check for method or property existence of an existing class?
There is a nice section in https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/AdvancedAppTricks/AdvancedAppTricks.html#//apple_ref/doc/uid/TP40007072-CH7-SW4 called Supporting Multiple Versions of iOS, which explains different techniques for Objective-C. How can these be translated to Swift?
Swift 2.0 provides us with a simple and natural way to do this.It is called API Availability Checking.Because NSURLQueryItem class is only available since iOS8.0,you can do in this style to check it at runtime.
if #available(iOS 8.0, *) {
// NSURLQueryItem is available
} else {
// Fallback on earlier versions
}
Simplest way I know of
if NSClassFromString("NSURLQueryItem") != nil {
println("NSURLQueryItem exists")
}else{
println("NSURLQueryItem does not exists")
}
Try this:
if objc_getClass("NSURLQueryItem") != nil {
// iOS 8
} else {
// iOS 7
}
I've also done it like this too:
if let theClass: AnyClass = NSClassFromString("NSURLQueryItem") {
// iOS 8
} else {
// iOS 7
}
Or, you can also check system version like so, but this isn't the best practice for iOS dev - really you should check if a feature exists. But I've used this for a few iOS 7 hacks... pragmatism over purity.
switch UIDevice.currentDevice().systemVersion.compare("8.0.0", options: NSStringCompareOptions.NumericSearch) {
case .OrderedSame, .OrderedDescending:
iOS7 = false
case .OrderedAscending:
iOS7 = true
}

Resources