What Is the Swift Preprocessor Equivalent to iOS Version Check Comparison

What's the Swift equivalent of Objective-C's #ifdef __IPHONE_11_0?

The iOS 11 SDK comes with Swift 3.2 (or Swift 4), so you can use a Swift version check to accomplish the same thing:

#if swift(>=3.2)
if #available(iOS 11.0, *) {

}
#endif

Swift: How to use preprocessors to add an extension method for *below* a certain iOS version?

I don't think that this is possible just relying on compile-time operations in Swift. If you care, by writing an extension in Objective-C that defines the method, your implementation will override the platform's when it exists, so that may be a viable and simple solution.

If you only want your implementation to kick in only if there is no native one, you should be able to do that somewhat easily in Objective-C, too. First, I have two warnings:

  1. You are modifying CoreData, which itself is already quite dynamic, and I'm not sure how it will react.
  2. I can't verify any of what I'm saying on this computer, so comment if it doesn't work :)

In your bridging header, ensure that the compiler knows that init(context:) is available regardless of the iOS version:

@interface NSManagedObject ()
+(void)initialize;
-(instancetype)initWithContext:(NSManagedObjectContext* _Nonnull)ctx;
@end

+initialize methods declared in categories are executed independently of whether the class has a +initialize method itself or if any other category has one.

The implementation will then look like this:

@implementation NSManagedObject ()

static id initWithContext(NSManagedObject* self, SEL sel, NSManagedObjectContext* context) {
// your implementation goes here
}

+(void)initialize {
SEL init = @selector(initWithContext:);
if (![self instancesRespondToSelector:init]) {
class_addMethod(self, init, (IMP)initWithContext, "@:@");
}
}

@end

Which is rather straightforward in my opinion: check if NSManagedObject supports initWithContext:, and if not, add your own implementation of it. You don't need to provide an explicit implementation of initWithContext:.

#if check (preprocessor macro) to differentiate between iPhone and iPad

There is no way to determine whether your app is built for iPhone or iPad. Preprocessor #if directives are resolved during build. Once your app is built and flagged as Universal, it has to run correctly on both devices. During building nobody knows where it will be installed later and one build can be installed on both.

However you may want to do one of these:

  1. Detect device model during runtime.

    To do this, use [[UIDevice currentDevice] model] and compare to iPhone, iPod touch or iPad strings. This will return you correct device even when running in compatibility mode on iPad (for iPhone-only apps). This can be usefull for usage analytics.

  2. Detect user interface idiom during runtime.

    This is what everyone checks for, when providing different content for iPhone and iPad. Use [[UIDevice currentDevice] userInterfaceIdiom] and compare to UIUserInterfaceIdiomPhone or UIUserInterfaceIdiomPad. You may want to make convenience methods like this:

    @implementation UIDevice (UserInterfaceIdiom)

    - (BOOL)iPhone {
    return (self.userInterfaceIdiom == UIUserInterfaceIdiomPhone);
    }
    + (BOOL)iPhone {
    return [[UIDevice currentDevice] iPhone];
    }

    - (BOOL)iPad {
    return (self.userInterfaceIdiom == UIUserInterfaceIdiomPad);
    }
    + (BOOL)iPad {
    return [[UIDevice currentDevice] iPad];
    }

    @end

    Then you can use:

    if ([[UIDevice currentDevice] iPhone]) { }
    // or
    if ([UIDevice iPhone]) { }
    // or
    if (UIDevice.iPhone) { }

How to check iOS version?

The quick answer …



As of Swift 2.0, you can use #available in an if or guard to protect code that should only be run on certain systems.

if #available(iOS 9, *) {}






In Objective-C, you need to check the system version and perform a comparison.

[[NSProcessInfo processInfo] operatingSystemVersion] in iOS 8 and above.

As of Xcode 9:

if (@available(iOS 9, *)) {}


The full answer …

In Objective-C, and Swift in rare cases, it's better to avoid relying on the operating system version as an indication of device or OS capabilities. There is usually a more reliable method of checking whether a particular feature or class is available.

Checking for the presence of APIs:

For example, you can check if UIPopoverController is available on the current device using NSClassFromString:

if (NSClassFromString(@"UIPopoverController")) {
// Do something
}

For weakly linked classes, it is safe to message the class, directly. Notably, this works for frameworks that aren't explicitly linked as "Required". For missing classes, the expression evaluates to nil, failing the condition:

if ([LAContext class]) {
// Do something
}

Some classes, like CLLocationManager and UIDevice, provide methods to check device capabilities:

if ([CLLocationManager headingAvailable]) {
// Do something
}

Checking for the presence of symbols:

Very occasionally, you must check for the presence of a constant. This came up in iOS 8 with the introduction of UIApplicationOpenSettingsURLString, used to load Settings app via -openURL:. The value didn't exist prior to iOS 8. Passing nil to this API will crash, so you must take care to verify the existence of the constant first:

if (&UIApplicationOpenSettingsURLString != NULL) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}

Comparing against the operating system version:

Let's assume you're faced with the relatively rare need to check the operating system version. For projects targeting iOS 8 and above, NSProcessInfo includes a method for performing version comparisons with less chance of error:

- (BOOL)isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion)version

Projects targeting older systems can use systemVersion on UIDevice. Apple uses it in their GLSprite sample code.

// A system version of 3.1 or greater is required to use CADisplayLink. The NSTimer
// class is used as fallback when it isn't available.
NSString *reqSysVer = @"3.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending) {
displayLinkSupported = TRUE;
}

If for whatever reason you decide that systemVersion is what you want, make sure to treat it as an string or you risk truncating the patch revision number (eg. 3.1.2 -> 3.1).

How to determine device type from Swift? (OS X or iOS)

If you're building for both iOS and macOS (and maybe for watchOS and tvOS, too), you're compiling at least twice: once for each platform. If you want different code to execute on each platform, you want a build-time conditional, not a run-time check.

Swift has no preprocessor, but it does have conditional build directives — and for the most part, they look like the C equivalent.

#if os(iOS) || os(watchOS) || os(tvOS)
let color = UIColor.red
#elseif os(macOS)
let color = NSColor.red
#else
println("OMG, it's that mythical new Apple product!!!")
#endif

You can also use build configurations to test for architecture (x86_64, arm, arm64, i386), Target environment (iOS simulator or Mac Catalyst), or -D compiler flags (including the DEBUG flag defined by the standard Xcode templates). Don’t assume that these things go together — Apple has announced macOS on arm64 to ship in 2020, so arm64 doesn’t imply iOS, and iOS Simulator doesn’t imply x86, etc.

See Compiler Control statements in The Swift Programming Language.

(If you want to distinguish which kind of iOS device you're on at runtime, use the UIDevice class just like you would from ObjC. It's typically more useful and safe to look at the device attributes that are important to you rather than a device name or idiom — e.g. use traits and size classes to lay out your UI, check Metal for the GPU capabilities you require, etc.)

Negate #available statement

In Swift 5.6, you can now do the following:

if #unavailable(iOS 15) {
// Under iOS 15
} else {
// iOS 15+
}

Or just the following depending on your case:

if #unavailable(iOS 15) {
// Under iOS 15
}

This is part of SE-0290: Negative availability.

iOS/Objective-c: How to get a macro that says the version is higher or equal to 13.0 in the .h file?

But you are allowed to use the preprocessor variables when defining your API

@property (nonatomic, retain) UIViewController *viewController NS_DEPRECATED_IOS(2.0, 13.0);
@property (nonatomic, retain) UIPopoverController *popoverController API_AVAILABLE(ios(13.0));

same for methods

-(void)halleluja:(Evil)brain API_AVAILABLE(ios(13.0)) {
//do evil stuff with brain
}

do not forget, your Apps targeted system version is meant here.

You will find plenty of different variations on how to in the documentations of (upper file part)
#import "Availability.h"
But you don't need to include it, Xcode does that for you.

You can also tell your pre-compiler

#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13
// code starting with ios(13.0)
[self halleluja:nope];
#else
// code earlier ios(13.0)
[self halleluja:papa]; //as defined above should throw a warning
#endif

and finally you can ask at runtime

if ([[[UIDevice currentDevice] systemVersion] isEqualToString:@"13.0"]) {
// code only on ios(13.0) and only ios(13.0)!
}

or

NSString *versionString = [[UIDevice currentDevice] systemVersion];
if ([versionString floatValue] >= 13.0) {
// not sexy - but works.
}

How can I programmatically find Swift's version?

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)
}


Related Topics



Leave a reply



Submit