11

How do I check the iOS deployment target in a Swift conditional compilation statement?

I've tried the following:

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
    // some code here
#else
    // other code here
#endif

But, the first expression causes the compile error:

Expected '&&' or '||' expression
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • http://stackoverflow.com/questions/24003291/ifdef-replacement-in-swift-language – Vincent Guerci Jun 23 '14 at 16:12
  • @VincentGuerci Thanks. I saw that, but it doesn't answer my (more specific) question. – ma11hew28 Jun 24 '14 at 17:12
  • everything you need is in this link, more particularly in apple documentation, I do not this think a built-in `__IPHONE_OS_VERSION_MIN_REQUIRED` or similar is available (yet?), but you could just use your own build configuration variables. or maybe trick using "Simple macros" imported into swift via a .h header. – Vincent Guerci Jun 24 '14 at 17:53
  • @VincentGuerci Yes, `__IPHONE_OS_VERSION_MIN_REQUIRED` is built-in. If you type it into one of your `.swift` files in Xcode and command-click on it, Xcode takes you to its declaration: `var __IPHONE_OS_VERSION_MIN_REQUIRED: CInt { get }`. I do not want to specify superfluous command line flags. I want to use the built-in one so that when I change the iOS deployment target in Xcode to iOS 8 (and up), Xcode compiles my code correctly. – ma11hew28 Jun 25 '14 at 00:17
  • I just saw [your answer](http://stackoverflow.com/a/24397402/242933). Thanks, for the thorough explanation. I'll just comment out my iOS 8 code for now. Then, once Apple publicly releases iOS 8, I'll stop supporting iOS < 8 (by deleting the legacy code and uncommenting my iOS 8 code). – ma11hew28 Jun 25 '14 at 00:26

5 Answers5

13

TL;DR? > Go to 3. Solution

1. Preprocessing in Swift

According to Apple documentation on preprocessing directives:

The Swift compiler does not include a preprocessor. Instead, it takes advantage of compile-time attributes, build configurations, and language features to accomplish the same functionality. For this reason, preprocessor directives are not imported in Swift.

That is why you have an error when trying to use __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 which is a C preprocessing directive. With swift you just can't use #if with operators such as <. All you can do is:

#if [build configuration]

or with conditionals:

#if [build configuration] && ![build configuration]

2. Conditional compiling

Again from the same documentation:

Build configurations include the literal true and false values, command line flags, and the platform-testing functions listed in the table below. You can specify command line flags using -D <#flag#>.

  • true and false: Won't help us
  • platform-testing functions: os(iOS) or arch(arm64) > won't help you, searched a bit, can't figure where they are defined. (in compiler itself maybe?)
  • command line flags: Here we go, that's the only option left that you can use...

3. Solution

Feels a bit like a workaround, but does the job: Other Swift flags

Now for example, you can use #if iOSVersionMinRequired7 instead of __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0, assuming, of course that your target is iOS7.

That basically is the same than changing your iOS deployment target version in your project, just less convenient... Of course you can to Multiple Build configurations with related schemes depending on your iOS versions targets.

Apple will surely improve this, maybe with some built in function like os()...

Vincent Guerci
  • 14,379
  • 4
  • 50
  • 56
  • 2
    There are now functions `os()` and `arch()`. https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_11 – fabb Sep 15 '14 at 07:23
7

Tested in Swift 2.2

By saying Deployment Target you mean iOS version, or App Target? Below I'm providing the solution if you have multiple versions of the app (free app, payed app, ...), so that you use different App Targets.

You can set custom Build configurations:
1. go to your project / select your target / Build Settings / search for Custom Flags
2. for your chosen target set your custom flag using -D prefix (without white spaces), for both Debug and Release
3. do above steps for every target you have

enter image description here

To differentiate between targets you can do something like this:

var target: String {
    var _target = ""
    #if BANANA
        _target = "Banana"
    #elseif MELONA
        _target = "Melona"
    #else
        _target = "Kiwi"
    #endif
    return _target
}

override func viewDidLoad() {
    super.viewDidLoad()
    print("Hello, this is target: \(target)"
}
Andrej
  • 7,266
  • 4
  • 38
  • 57
  • Odd curiosity of mine. Obviously this works, and it's fantastic. Thank you! But what does the "-D" prefix do? What is it for? – Theo Bendixson Sep 26 '16 at 14:21
  • 1
    @TheoBendixson it's to tell the compiler that what follows should be treated as a custom flag. For example, if you wanted to set a flag that started with "Og" for whatever reason, that would be misinterpreted as trying to pass the "-O" and "-g" flags at the same time. – thislooksfun Dec 17 '16 at 20:37
  • Will it work in Release as well? (D is not only for debug right?) – Roi Mulia Jul 25 '17 at 07:42
  • @RoiMulia True, `D` is not related to "debug". It works for release as well. – Andrej Jul 25 '17 at 22:59
1

I have come across this issue recently when building a library whose source code supports multiple iOS and macOS versions with different functionality.

My solution is using custom build flags in Xcode which derive their value from the actual deployment target:

TARGET_IOS_MAJOR = TARGET_IOS_MAJOR_$(IPHONEOS_DEPLOYMENT_TARGET:base)
TARGET_MACOS_MAJOR = TARGET_MACOS_MAJOR_$(MACOSX_DEPLOYMENT_TARGET:base)

Referring to those user defined settings in Others Swift Flags like:

OTHER_SWIFT_FLAGS = -D$(TARGET_MACOS_MAJOR) -D$(TARGET_IOS_MAJOR)

allows me to check for the actual major OS version in my Swift sources as follows:

#if os(macOS)
    #if TARGET_MACOS_MAJOR_12
        #warning("TARGET_MACOS_MAJOR_12")

    #elseif TARGET_MACOS_MAJOR_11
        // use custom implementation
        #warning("TARGET_MACOS_MAJOR_11")
    #endif

#elseif os(iOS)
    #if TARGET_IOS_MAJOR_15
        #warning("TARGET_IOS_MAJOR_15")

    #elseif TARGET_IOS_MAJOR_14
        #warning("TARGET_IOS_MAJOR_14")

    #else
        #warning("older iOS")
    #endif
#endif

Currently I don't know if a similar approach would be possible in a SPM package. This is something I will try to do in a later phase.

pd95
  • 1,999
  • 1
  • 20
  • 33
0

You can't do it in a conditional compilation statement like that. "Complex macros" as Apple calls them are not supported in Swift. Generics and types do the same thing, in their mind, with better results. (Here's a link they published https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_13)

Here's a function I came up with that accomplishes the same thing (and obviously just replace the string returns with whatever is useful for you like a boolean or just the option itself):

func checkVersion(ref : String) -> String {
    let sys = UIDevice.currentDevice().systemVersion
    switch sys.compare(ref, options: NSStringCompareOptions.NumericSearch, range: nil, locale: nil) {
    case .OrderedAscending:
        return ("\(ref) is greater than \(sys)")
    case .OrderedDescending:
        return ("\(ref) is less than \(sys)")
    case .OrderedSame:
        return ("\(ref) is the same as \(sys)")
    }
}

// Usage
checkVersion("7.0") // Gives "7.0 is less than 8.0"
checkVersion("8.0") // Gives "8.0 is the same as 8.0"
checkVersion("8.2.5") // Gives "8.2.5 is greater than 8.0"
Dylan Gattey
  • 1,713
  • 13
  • 34
  • Thanks, but that's a *runtime* solution. I'm looking for a *compile-time* solution. I'll just comment out my iOS 8 code for now. Then, once Apple publicly releases iOS 8, I'll stop supporting iOS < 8 (by deleting the legacy code and uncommenting my iOS 8 code). – ma11hew28 Jun 24 '14 at 17:21
  • 3
    @MattDiPasquale If you have a compelling use case for a compile-time solution, you should file an enhancement request with Apple. You'd be helping everyone! – matt Jun 24 '14 at 22:49
  • @matt I don't remember why I wanted a compile-time solution. Maybe I didn't have a good reason, but based on [my comment above](https://stackoverflow.com/questions/24369272/swift-ios-deployment-target-command-line-flag/38635296#comment37728510_24372145), I guess I was getting ahead of myself by writing code for iOS 8 while iOS 8 was still in beta and wanted to be able to just change the iOS deployment target (specified in Xcode) from 8 to 7 before archiving my app for the App Store. And I guess I didn't want to create a new branch or to make my app run differently on different versions of iOS. – ma11hew28 Feb 15 '22 at 16:53
0

I know your question is been here for a while but just in case someone's still looking for an answer they should know that starting with Swift 2.0 you can do something like this:

if #available(iOS 8, *) {
    // iOS 8+ code
} else {
    // older iOS code
}

You can read more about it here.

Mihai Fratu
  • 7,579
  • 2
  • 37
  • 63
  • This checks during runtime, but my question asks how to check during compile time. – ma11hew28 Feb 14 '22 at 17:07
  • I’m pretty sure that you can’t compile this while you have code that’s marked as available starting with iOS 8 (as in the example I gave) in the “older” bracket So maybe I’m not getting what you mean exactly by compile time vs runtime but I’d say this is not a runtime check – Mihai Fratu Feb 14 '22 at 23:30
  • Never mind :) On a second thought I know what you meant :P It’s late :) Long story short: both branches must compile and type-check. – Mihai Fratu Feb 14 '22 at 23:32
  • But then, I guess this raises the question: why would you need an explicit compile time check when this would most likely do the trick? – Mihai Fratu Feb 14 '22 at 23:42
  • 1
    Good question. I tried to answer it in [this comment](https://stackoverflow.com/questions/24369272/swift-ios-deployment-target-command-line-flag/38635296#comment125736818_24372145). – ma11hew28 Feb 15 '22 at 16:44