iOS 11.1 feature in Xcode 9.0 - ios

(This is based on an issue here: https://github.com/dokun1/Lumina/issues/44)
Consider the following function:
fileprivate var discoverySession: AVCaptureDevice.DiscoverySession? {
var deviceTypes = [AVCaptureDevice.DeviceType]()
deviceTypes.append(.builtInWideAngleCamera)
if #available(iOS 10.2, *) {
deviceTypes.append(.builtInDualCamera)
}
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
return AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified)
}
I am running Xcode 9.0. I want to run a framework that makes use of this feature in iOS 11.1, which is only available in Xcode 9.1. The code in this function that gives an error is:
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
When running on Xcode 9.1 on someone else's machine, this works fine, and the application developing with this framework can set a development target of 10.0, and it compiles fine. However, I can't even build the framework on my machine. The error I get reads Type 'AVCaptureDevice.DeviceType' has no member 'builtInTrueDepthCamera' in Xcode 9.0 I thought using the #available macro would fix this, but it doesn't work that well.
I've also tried to use this:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 111000
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
#endif
But this causes an error reading: Expected '&&' or '||' expression
Anyone know what to do?

#available will raise the "SDK Level" so that the compiler will allow you to use API calls above your Deployment target, but it won't prevent the compiler from compiling the lines inside the #available scope.
You need to prevent the compiler from compiling those lines because the compiler doesn't have a definition for .builtInTrueDepthCamera. You can do that using the #if build configuration statement.
In this case you want to check for swift version 4.0.2. Xcode 9.1 shipped with Swift 4.0.2.
#if swift(>=4.0.2)
if #available(iOS 11.1, *), self.captureDepthData == true {
deviceTypes.append(.builtInTrueDepthCamera)
}
#endif
source: https://www.bignerdranch.com/blog/hi-im-available/#what-it-is-not

Related

Getting Error while upgrading to new Xcode 12

My App is using CoreLocation and CLLocationManager and is working fine in iOS 13 and iOS 12.
I have implemented new feature of Precise Location in iOS 14 using Xcode 12 and its working fine in iOS 14, iOS 13, iOS 12.
But When I execute ths Xcode 12 code in Xcode 11 version (Xcode 11.7) then I am getting error
Cannot infer contextual base in reference to member 'reducedAccuracy'
Value of type 'CLLocationManager' has no member 'accuracyAuthorization'
if #available(iOS 14.0, *) {
if authorizationStatus.accessLevel == .granted && locationManager.accuracyAuthorization == .reducedAccuracy {
return .locationAlwaysAllowPreciseLocationOff
}
if authorizationStatus.accessLevel == .denied && locationManager.accuracyAuthorization == .fullAccuracy {
return .locationDeniedPreciseLocationON
}
}
// MARK: iOS 14 location function.
#available(iOS 14.0, *)
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
// iOS 14 Location Delegate method, not available in iOS 13 version
}
and here the error is
Static member 'authorizationStatus' cannot be used on instance of type 'CLLocationManager'
As i Know Precise Location is feature of iOS 14 and its not available in below versions and "accuracyAuthorization", ".reducedAccuracy", ".fullAccuracy" is not available in iOS 13 versions.
My Question is how can i make my code run in Xcode 11 versions. I have already added the isAvailable check to check the device version.
Thanks in advance :)
No amount of #available or #available marking is going to help you in this situation.
Why not? Well, you're doing an unexpected thing: you are opening an Xcode 12 project in Xcode 11. Your code was compiled originally in Xcode 12, where iOS 14 is a thing. So it compiled successfully. But now you open the same project in Xcode 11, where iOS 14 is not a thing. Nothing about this environment has the slightest idea that it exists. Therefore, code that involves something unique to iOS 14 will not compile. If the compiler sees that code, you are toast.
So is all hope lost? Not quite! Suppose we were to hide the code from the compiler. If we do that — if we can arrange things so that, in Xcode 11, the compiler never sees this code at all — then we will be able to compile in Xcode 11.
Well, we can do that! We can use a compilation condition. All we need is some condition that we are allowed to check against, that will distinguish what version of Xcode this is. And there is such a condition — the Swift version.
So, we can write this, for example:
class ViewController: UIViewController {
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
#if swift(>=5.3)
let status = manager.authorizationStatus
print(status.rawValue)
#endif
}
}
That code compiles in both Xcode 12 and Xcode 11, because in Xcode 11 the compilation condition fails, and the compiler never even looks inside the #if block.
In fact, we can provide an alternative version of the code, to be used in Xcode 11. In order to make this work as we desire, we will also have to restore your #available check, because we have to make the project's deployment target iOS 13, and the Xcode 12 compiler will complain if we don't protect the iOS 14 code:
class ViewController: UIViewController {
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
#if swift(>=5.3)
if #available(iOS 14.0, *) {
let status = manager.authorizationStatus
print(status.rawValue)
}
#else
let status = CLLocationManager.authorizationStatus()
print(status.rawValue)
#endif
}
}
That code compiles and behaves correctly in either Xcode 11 or Xcode 12. Do you understand why? Let's review, because it's a bit tricky.
In Xcode 11, the whole #if section is never seen by the compiler. It sees only this:
let status = CLLocationManager.authorizationStatus()
print(status.rawValue)
That's good iOS 13 code, so all is well.
In Xcode 12, the whole #else section is never seen by the compiler. It sees only this:
if #available(iOS 14.0, *) {
let status = manager.authorizationStatus
print(status.rawValue)
}
That's good iOS 14 code, because, even though our project's deployment target is iOS 13, we have calmed the compiler's nerves by guaranteeing that this code won't execute in iOS 13 (where it would crash if it did execute).
Having said all that, the real answer is: don't. Everything I just did is way too much trouble! Once you've written code under Xcode 12, don't try to open that project in Xcode 11. That's not the way to test for backward compatibility.

How to get the minimum deployment target in the xcode project

I want to retrieve the minimum deployment target in the xcode project.
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
I wanted to check whether the version is 13.2 or lesser.
Is there any way or any command which returns the deployment target.
I want to read it from the ruby code.
you can check the target version by below code and perform your desired action
In Swift:
if #available(iOS 13.2, *) {
// your code for iOS 13.2 or above versions
} else {
// Fallback on earlier versions or your code for earlier versions
}
In Objective C:
if (#available(iOS 13.2, *)) {
// your code for iOS 13.2 or above versions
} else {
// Fallback on earlier versions or your code for earlier versions
}
In Ruby:
Using the plist gem, like this:
require 'plist'
info = Plist.parse_xml('path/to/Info.plist')
puts info["CFBundleShortVersionString"]
puts info["CFBundleVersion"]
Use this reference: How to get target's version by Ruby (or Xcodeproj)
also, https://www.rubydoc.info/gems/xcodeproj/Xcodeproj/Project/Object/XCConfigurationList

Cannot add #if compiler(>=5.0) in `AppDelegate.m` file:

Trying to add a block in the AppDelegate.m file
#if compiler(>=5.0)
if (#available(iOS 13.0, *)) {
// do something...
}
#endif
The error showed is:
Function-like macro 'compiler' is not defined
The following is a Swift compile-time directive:
#if compiler(>=5.0)
...
#endif
It makes no sense to use this in an Objective-C file, AppDelegate.m. In an Objective-C source, it will result in the “Function-like macro 'compiler' is not defined” error.
So, if you want a runtime check for iOS 13, just remove the #if, e.g.:
if (#available(iOS 13.0, *)) {
...
}
See Marking API Availability in Objective-C.
A couple of observations (given that you tagged this with swift):
In Swift, the compile-time pattern is:
#if swift(>=5.0)
...
#endif
Or
#if compiler(>=5.0)
...
#endif
This pattern is generally only used when writing third-party libraries where you need to offer backward support to developers who are compiling your library with earlier versions of Swift.
See The Swift Programming Language: Compiler Control Statements
The following is an Objective-C runtime check for OS version:
if (#available(iOS 13.0, *)) {
// do something...
}
The equivalent Swift pattern is:
if #available(iOS 13.0, *) {
...
}
These are useful when writing code that is targeting multiple iOS versions, but you want to conditionally run code using iOS 13+ API.
To make it confusing, Swift does have an #available construct, but it is for marking a class, extension, or method as being available only for devices running a particular OS version, but is not used in conjunction with an if test. E.g. let’s say you are defining some function that should only be available to iOS 13 and later:
#available(iOS 13.0, *)
func someMethod() {
...
}
This is compile-time construct is to tell the compiler to not complain about iOS 13 API within this function if your app happens to support earlier iOS versions. You are effectively telling the compiler “hey, I know this has code inside it that only works with iOS 13 and later, but don’t worry, because I’ll only call it when the device in question is running iOS 13 or later.”
There are many things wrong that you did in your code example:
#if is a preprocessor macro. You can only use it outside the scope of anything, just like the import statement. If you are using it the way it should be used, then you can't use that if statement there.
#available cannot be used inside of an if statement. It can only be used to represent if a particular class/struct/func is available for said OS version and up, but not fragments of code. For that, use this instead:
if #available(iOS 13.0, *) {
// Do something.
}
If you want to check the Swift compiler version specifically, you can use #if swift. To check for iOS version during runtime, you need to use #available, the # version if used to mark types/functions only available on said versions.
#if swift(>=5.0)
if #available(iOS 13,*) {
//do something
}
#endif

Keeping NSUserActivity backwards compatible with Xcode 9

Using Xcode 10 (beta 6) I am able to write and run the following code with no trouble:
import Intents
func test() {
let activity = NSUserActivity(activityType: "com.activtiy.type")
activity.title = "Hello World"
activity.isEligibleForSearch = true
activity.isEligibleForHandoff = false
if #available(iOS 12.0, *) {
activity.isEligibleForPrediction = true
activity.suggestedInvocationPhrase = "Say something"
}
print(activity)
}
As of iOS 12 the .isEligibleForPredictions and .suggestedInvocationPhrase properties have been added, so Xcode 10 can keep the code itself backwards compatible using the if #available conditional.
However, I want to ensure this code is backwards compatible with earlier versions of Xcode. When run in Xcode 9, I get the following errors:
if #available(iOS 12.0, *) {
// ERROR: Value of type 'NSUserActivity' has no member 'isEligibleForPrediction'
activity.isEligibleForPrediction = true
// ERROR: Value of type 'NSUserActivity' has no member 'suggestedInvocationPhrase'
activity.suggestedInvocationPhrase = "Say something"
}
This appears to be because the #available macro is actually resolved at runtime, therefore all code contained still needs to be compiled successfully.
Is there a way for me to tell the compiler to just ignore these two lines of code when building for iOS 11, or using Xcode 9?
Xcode 10 uses Swift 4.2 while Xcode 9 uses Swift 4.1. So you can use that knowledge at compile time:
func test() {
let activity = NSUserActivity(activityType: "com.activtiy.type")
activity.title = "Hello World"
activity.isEligibleForSearch = true
activity.isEligibleForHandoff = false
#if swift(>=4.2) // compile-time check
if #available(iOS 12.0, *) { // run-time check
activity.isEligibleForPrediction = true
activity.suggestedInvocationPhrase = "Say something"
predictionApiAvailable = true
}
#endif
print(activity)
}
(This answer assumes that you are using Swift 4.2 on Xcode 10.)
The Availability Condition (if #available) you are using as you correctly noted is evaluated at run-time, but the compiler will use this information to provide compile-time safety (It will will warn you if you are calling an API that does not exist min deployment target).
For conditionally compiling code you must use Conditional Compilation Block
#if compilation condition
statements
#endif
To conditionally build your code based on the Xcode version you can include a Active Compilation Condition in the build settings (a -D swift compile flag) that its name is dynamically created based on the Xcode version. Then use this as compilation condition. Xcode already provides the build setting XCODE_VERSION_MAJOR that resolves to the current Xcode version( e.g. 0900 for Xcode 9). So you can add an an Active Compilation Condition with name XCODE_VERSION_$(XCODE_VERSION_MAJOR) that will resolve to the flag XCODE_VERSION_0900 for Xcode 9.
Then you can conditionally compile your code using:
#if XCODE_VERSION_0900
statements
#endif
I have an example project here

How can I programmatically find Swift's version?

I know I can find the version of Swift I'm running right now reverting to a Terminal and typing:
xcrun swift --version
Swift version 1.1 (swift-600.0.57.4)
Target: x86_64-apple-darwin13.4.0
Also, I've been reading about the Preprocessor Macros in Swift, but no luck finding a Swift version constant.
As Swift 1.2 approaches it will be nice to flag old code that only runs on Swift 1.1 (Xcode up to 6.2) or new code that needs Xcode 6.3 (Swift 1.2)
Note: I can also use system() to do something like:
system("xcrun swift --version | grep version > somefile.txt")
Then open somefile.txt, but rather prefer some simpler solution
You can use conditional compilation directives to test for the specific Swift version used to build your project:
#if swift(>=5.0)
print("Hello, Swift 5!")
#elseif swift(>=4.0)
print("Hello, Swift 4!")
#elseif swift(>=3.0)
print("Hello, Swift 3!")
#elseif swift(>=2.2)
print("Hello, Swift 2.2!")
#elseif swift(>=2.1)
print("Hello, Swift 2.1!")
#endif
Finally got a workaround to do this. I'm using the constants prefixed with __ you can observe in your Playground. This would have been easier with some level of reflection, but...
__IPHONE_OS_VERSION_MAX_ALLOWED is 80200, meaning __IPHONE_8_2 for Xcode 6.2 (Swift 1.1) but its value is 80300 (__IPHONE_8_3) in Xcode 6.3 (Swift 1.2)
func isSwift12() -> Bool {
return __IPHONE_OS_VERSION_MAX_ALLOWED == 80300
}
isSwift12()
So now in your library you can fail fast and tell your user Swift's version is not correct using this:
assert(isSwift12(), "Need Swift 12")
Swift will give you a nice:
assertion failed: Need Swift 12: file , line 20
UPDATE WWDC 2015 - Swift 2.0
As stated in Apple's Swift blog, in Swift 2.0 we have #available blocks to check for certain OS versions in our code. An example should be:
if #available(OSX 10.11, *) {
monochromeFilter!.setValue(CIColor(red: 0.5, green: 0.5, blue: 0.5), forKey:kCIInputColorKey)
} else {
// Fallback on earlier versions
}
Swift 3.1 extends the #available attribute to support specifying Swift version numbers in addition to its existing platform versions.
// Swift 3.1
#available(swift 3.1)
func intVersion(number: Double) -> Int? {
return Int(exactly: number)
}
#available(swift, introduced: 3.0, obsoleted: 3.1)
func intVersion(number: Double) -> Int {
return Int(number)
}
From your comment:
I want to check because different versions of Swift have different features
You should not check the version of your programming language in order to use some features or not. This approach is much better:
if (self.respondsToSelector(Selector("yourMethodSelector"))) {
self.yourMethodSelector(test, sender: self)
} else {
//error handling
}
Just check whether a method is available or not.
Open up a command line on your Mac computer
type swift -version, without the double dashes, which doesn't work any more for Xcode 11/Swift 5.2.2
Of course, you'll have to update to latest IDE, which always downloads the latest version of both Xcode, languages, compilers and tools... EVERYTHING is updated. If you do that, that solution works for sure.
For iOS :
var systemVersion = UIDevice.currentDevice().systemVersion;
For OSX :
var systemVersion = NSProcessInfo.processInfo().operatingSystemVersion;
K.

Resources