Swift: iOS Deployment Target Command Line Flag
Asked Answered
S

5

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
Sollars answered 23/6, 2014 at 15:6 Comment(5)
#24003791Cystoid
@VincentGuerci Thanks. I saw that, but it doesn't answer my (more specific) question.Sollars
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.Cystoid
@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.Sollars
I just saw your answer. 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).Sollars
C
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()...

Cystoid answered 24/6, 2014 at 22:30 Comment(1)
There are now functions os() and arch(). developer.apple.com/library/prerelease/ios/documentation/Swift/…Verbal
E
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)"
}
Extrapolate answered 8/4, 2016 at 14:37 Comment(4)
Odd curiosity of mine. Obviously this works, and it's fantastic. Thank you! But what does the "-D" prefix do? What is it for?Chlorate
@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.Inexpressible
Will it work in Release as well? (D is not only for debug right?)Loughlin
@RoiMulia True, D is not related to "debug". It works for release as well.Extrapolate
I
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.

Imply answered 13/2, 2022 at 10:55 Comment(0)
I
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"
Incubate answered 23/6, 2014 at 17:47 Comment(3)
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).Sollars
@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!Campstool
@Campstool 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, 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.Sollars
L
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.

Laos answered 28/7, 2016 at 11:40 Comment(5)
This checks during runtime, but my question asks how to check during compile time.Sollars
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 😬Laos
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.Laos
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? 🙄Laos
Good question. I tried to answer it in this comment.Sollars

© 2022 - 2024 — McMap. All rights reserved.