Swift iOS version availability - check from * to iOS 13 - ios

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.

Related

Enabling keyboard shortcuts on iPad for app that supports iOS 12 and 13

I have an iOS app that supports iOS 10 up-to iOS 13 and recently added Catalyst support to it. Through an extension to AppDelegate keyboard short-cuts are supported, and I would like to enable them on iPad as well.
extension AppDelegate {
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
guard builder.system == .main else { return }
// Add menus and shortcuts
}
}
This compiles fine on the Catalyst target, but when building for iOS the following error is given: 'UIMenuBuilder' is only available in iOS 13.0 or newer
The obvious solution is put an availability check in:
#available(iOS 13.0, *)
extension AppDelegate {
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
guard builder.system == .main else { return }
// Add menus and shortcuts
}
}
but then the error changes to Overriding 'buildMenu' must be as available as declaration it overrides.
So for now I excluded the extension from the build on iOS to get a working build, but that means no short-cuts on iPad.
I faced the same exact issue as you. Use:
#if targetEnvironment(macCatalyst)
around your extension instead of the #available suggestion by Xcode.

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.

Swift - Use both UIAlertController & UIAlertView for iOS 7 / iOS 8 and above

My app's deployment target is 7.0 . I want to use both UIAlertController and UIAlertView. I read somewhere that checking for iOS versions is not good, so i used this code :
if (NSClassFromString("UIAlertController") != nil) {
// UIAlertController
} else {
// UIAlertView
But even if do that, i still get that "correctable" error "UIAlertController is only available on iOS 8.0 or newer" and i have to choose between 3 'Fix-it' options :
Add 'if #available' version check ( if #available(iOS 8.0, *) { ... } else { ... })
Add #available attribute to enclosing instance method
Add #available attribute to enclosing class
What should i do ? Currently using Xcode 7 GM
As stated, the best way to do this is using the #available function. I've attached a code example for you.
if #available(iOS 8.0, *) {
} else {
}
#available is the best way to do these checks.

Detecting available API iOS vs. watchOS in Swift

#available does not seem to work when differentiating between watchOS and iOS.
Here is an example of code shared between iOS & watchOS:
lazy var session: WCSession = {
let session = WCSession.defaultSession()
session.delegate = self
return session
}()
...
if #available(iOS 9.0, *) {
guard session.paired else { throw WatchBridgeError.NotPaired } // paired is not available
guard session.watchAppInstalled else { throw WatchBridgeError.NoWatchApp } // watchAppInstalled is not available
}
guard session.reachable else { throw WatchBridgeError.NoConnection }
Seems that it just defaults to WatchOS and the #available is not considered by the compiler.
Am I misusing this API or is there any other way to differentiate in code between iOS and WatchOS?
Update: Seems like I was misusing the API as mentioned by BPCorp
Using Tali's solution for above code works:
#if os(iOS)
guard session.paired else { throw WatchBridgeError.NotPaired }
guard session.watchAppInstalled else { throw WatchBridgeError.NoWatchApp }
#endif
guard session.reachable else { throw WatchBridgeError.NoConnection }
Unfortunately there is no #if os(watchOS) .. as of Xcode 7 GM
Edit: Not sure when it was added but you can now do #if os(watchOS) on Xcode 7.2
If you want to execute that code only on iOS, then use #if os(iOS) instead of the if #available(iOS ...).
This way, you are not using a dynamic check for the version of your operating system, but are compiling a different code for one OS or the other.
In the Apple dev guide, it is said that the star, * (which is required) means that it will execute the if body for OSes not specified but listed in the minimum deployment target specified by your target.
So, if your target specifies iOS and watchOS, your statement if #available(iOS 9.0, *) means that the ifbody is available for iOS 9 and later and any watchOS version.
Also, be careful if you want to use what's described in the chapter "Build Configurations" in this Apple guide. It is used to conditionally compile your code based on the operating system. This is not dynamic at runtime.
With the GM version of Xcode7 I think they fixed that issue. For me :
if #available(watchOS 2,*) {
// Only if using WatchOS 2 or higher
}
is working fine in GM version.
Time have passed since the question. If somebody still looking for the answer, need to say that
#if os(watchOS)
is now available in Xcode 13 and later.

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