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
}
}
}
Related
I am aware that NSWindow is not officially available in Mac Catalyst but I have seen several posts and success stories accessing it via NSClassFromString and valueForKeyPath. However, I have never been able to get this to work, as it always returns nil.
guard let nsWindows = NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.windows") as? [AnyObject] else {
return nil
}
Have also attempted in objective c with
NSArray *const nsWindows = [NSClassFromString(#"NSApplication") valueForKeyPath:#"sharedApplication.windows"];
But the same result - nil - every time.
Is there a trick to getting this to work, or has this way been shut down as a potential way to access it? It successfully can get the NSApplication instance. But the windows property is always nil.
This can be done easily with the help of Dynamic:
extension UIWindow {
var nsWindow: NSObject? {
var nsWindow = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(self)
if #available(macOS 11, *) {
nsWindow = nsWindow.attachedWindow
}
return nsWindow.asObject
}
}
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.
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.
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?
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()
}
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.
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()
}
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
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
}