Detecting available API iOS vs. watchOS in Swift - ios

#available does not seem to work when differentiating between watchOS and iOS.
Here is an example of code shared between iOS & watchOS:
lazy var session: WCSession = {
let session = WCSession.defaultSession()
session.delegate = self
return session
}()
...
if #available(iOS 9.0, *) {
guard session.paired else { throw WatchBridgeError.NotPaired } // paired is not available
guard session.watchAppInstalled else { throw WatchBridgeError.NoWatchApp } // watchAppInstalled is not available
}
guard session.reachable else { throw WatchBridgeError.NoConnection }
Seems that it just defaults to WatchOS and the #available is not considered by the compiler.
Am I misusing this API or is there any other way to differentiate in code between iOS and WatchOS?
Update: Seems like I was misusing the API as mentioned by BPCorp
Using Tali's solution for above code works:
#if os(iOS)
guard session.paired else { throw WatchBridgeError.NotPaired }
guard session.watchAppInstalled else { throw WatchBridgeError.NoWatchApp }
#endif
guard session.reachable else { throw WatchBridgeError.NoConnection }
Unfortunately there is no #if os(watchOS) .. as of Xcode 7 GM
Edit: Not sure when it was added but you can now do #if os(watchOS) on Xcode 7.2

If you want to execute that code only on iOS, then use #if os(iOS) instead of the if #available(iOS ...).
This way, you are not using a dynamic check for the version of your operating system, but are compiling a different code for one OS or the other.

In the Apple dev guide, it is said that the star, * (which is required) means that it will execute the if body for OSes not specified but listed in the minimum deployment target specified by your target.
So, if your target specifies iOS and watchOS, your statement if #available(iOS 9.0, *) means that the ifbody is available for iOS 9 and later and any watchOS version.
Also, be careful if you want to use what's described in the chapter "Build Configurations" in this Apple guide. It is used to conditionally compile your code based on the operating system. This is not dynamic at runtime.

With the GM version of Xcode7 I think they fixed that issue. For me :
if #available(watchOS 2,*) {
// Only if using WatchOS 2 or higher
}
is working fine in GM version.

Time have passed since the question. If somebody still looking for the answer, need to say that
#if os(watchOS)
is now available in Xcode 13 and later.

Related

Enabling keyboard shortcuts on iPad for app that supports iOS 12 and 13

I have an iOS app that supports iOS 10 up-to iOS 13 and recently added Catalyst support to it. Through an extension to AppDelegate keyboard short-cuts are supported, and I would like to enable them on iPad as well.
extension AppDelegate {
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
guard builder.system == .main else { return }
// Add menus and shortcuts
}
}
This compiles fine on the Catalyst target, but when building for iOS the following error is given: 'UIMenuBuilder' is only available in iOS 13.0 or newer
The obvious solution is put an availability check in:
#available(iOS 13.0, *)
extension AppDelegate {
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
guard builder.system == .main else { return }
// Add menus and shortcuts
}
}
but then the error changes to Overriding 'buildMenu' must be as available as declaration it overrides.
So for now I excluded the extension from the build on iOS to get a working build, but that means no short-cuts on iPad.
I faced the same exact issue as you. Use:
#if targetEnvironment(macCatalyst)
around your extension instead of the #available suggestion by Xcode.

Checking for quick actions availability

I'm using home screen quick actions that's only supported in IOS9.
Using the constant UIApplicationLaunchOptionsShortcutItemKey will crash if used in IOS8.
What is the correct way to check if quick actions is supported?
One way is to check for IOS9 through systemVersion but I'm hoping there is a better way.
[[UIDevice currentDevice] systemVersion]
In objective C you can check to see if a class exists. Say something like
if([UIApplicationShortcutItem class]){
//Handle shortcut launch
}
I think in case of Swift the best way for checking the API availability is Automatic operating system API availability checking that's new feature released with iOS9 and Swift2
if #available(iOS 9, *) {
// use UIApplicationLaunchOptionsShortcutItemKey
} else {
// no available
}
#available is going to check whether are we using iOS 9 or later, or any other unknown platforms like watchOS so the * is also here.
If your code is inside a function then you can use #available with guard like this.
guard #available(iOS 9, *) else {
return
}
Mark your methods and class as well like
#available(iOS 9, *)
func useMyStackView() {
// use UIStackView
}
#available works similarly to #available so If your deployment target is iOS7 or less than 9, you can't call that useMyStackView()
For Objc, it also can use #available (iOS 9, *) to check the OS version
if (#available(iOS 9, *)) {
//use UIApplicationLaunchOptionsShortcutItemKey
}

How do I change a constant based on the build target in Swift? [duplicate]

In Objective-C we can know if an app is being built for device or simulator using macros:
#if TARGET_IPHONE_SIMULATOR
// Simulator
#else
// Device
#endif
These are compile time macros and not available at runtime.
How can I achieve the same in Swift?
Update 30/01/19
While this answer may work, the recommended solution for a static check (as clarified by several Apple engineers) is to define a custom compiler flag targeting iOS Simulators. For detailed instructions on how to do to it, see #mbelsky's answer.
Original answer
If you need a static check (e.g. not a runtime if/else) you can't detect the simulator directly, but you can detect iOS on a desktop architecture like follows
#if (arch(i386) || arch(x86_64)) && os(iOS)
...
#endif
After Swift 4.1 version
Latest use, now directly for all in one condition for all types of simulators need to apply only one condition -
#if targetEnvironment(simulator)
// your simulator code
#else
// your real device code
#endif
For more clarification, you can check Swift proposal SE-0190
For older version -
Clearly, this is false on a device, but it returns true for the iOS Simulator, as specified in the documentation:
The arch(i386) build configuration returns true when the code is compiled for the 32–bit iOS simulator.
If you are developing for a simulator other than iOS, you can simply vary the os parameter: e.g.
Detect the watchOS simulator
#if (arch(i386) || arch(x86_64)) && os(watchOS)
...
#endif
Detect the tvOS simulator
#if (arch(i386) || arch(x86_64)) && os(tvOS)
...
#endif
Or, even, detect any simulator
#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(watchOS) || os(tvOS))
...
#endif
If you instead are ok with a runtime check, you can inspect the TARGET_OS_SIMULATOR variable (or TARGET_IPHONE_SIMULATOR in iOS 8 and below), which is truthy on a simulator.
Please notice that this is different and slightly more limited than using a preprocessor flag. For instance you won't be able to use it in place where a if/else is syntactically invalid (e.g. outside of functions scopes).
Say, for example, that you want to have different imports on the device and on the simulator. This is impossible with a dynamic check, whereas it's trivial with a static check.
#if (arch(i386) || arch(x86_64)) && os(iOS)
import Foo
#else
import Bar
#endif
Also, since the flag is replaced with a 0 or a 1 by the swift preprocessor, if you directly use it in a if/else expression the compiler will raise a warning about unreachable code.
In order to work around this warning, see one of the other answers.
OUTDATED FOR SWIFT 4.1. Use #if targetEnvironment(simulator) instead. Source
To detect simulator in Swift you can use build configuration:
Define this configuration -D IOS_SIMULATOR in Swift Compiler - Custom Flags > Other Swift Flags
Select Any iOS Simulator SDK in this drop down
Now you could use this statement to detect simulator:
#if IOS_SIMULATOR
print("It's an iOS Simulator")
#else
print("It's a device")
#endif
Also you could extend UIDevice class:
extension UIDevice {
var isSimulator: Bool {
#if IOS_SIMULATOR
return true
#else
return false
#endif
}
}
// Example of usage: UIDevice.current.isSimulator
Updated Info as of February 20, 2018
It looks like #russbishop has an authoritative answer that renders this answer "incorrect" - even though it appeared to work for a long time.
Detect if app is being built for device or simulator in Swift
Previous Answer
Based on #WZW's answer and #Pang's comments, I created a simple utility struct. This solution avoids warning produced by #WZW's answer.
import Foundation
struct Platform {
static var isSimulator: Bool {
return TARGET_OS_SIMULATOR != 0
}
}
Example usage:
if Platform.isSimulator {
print("Running on Simulator")
}
Swift 4
You can now use targetEnvironment(simulator) as an argument.
#if targetEnvironment(simulator)
// Simulator
#else
// Device
#endif
Updated for Xcode 9.3
From Xcode 9.3
#if targetEnvironment(simulator)
Swift supports a new platform condition targetEnvironment with a
single valid argument simulator. Conditional compilation of the form
'#if targetEnvironment(simulator)' can now be used to detect when the build target is a simulator. The Swift compiler will attempt to
detect, warn, and suggest the use of targetEnvironment(simulator) when
evaluating platform conditions that appear to be testing for simulator
environments indirectly, via the existing os() and arch() platform
conditions. (SE-0190)
iOS 9+:
extension UIDevice {
static var isSimulator: Bool {
return NSProcessInfo.processInfo().environment["SIMULATOR_DEVICE_NAME"] != nil
}
}
Swift 3:
extension UIDevice {
static var isSimulator: Bool {
return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
}
}
Before iOS 9:
extension UIDevice {
static var isSimulator: Bool {
return UIDevice.currentDevice().model == "iPhone Simulator"
}
}
Objective-C:
#interface UIDevice (Additions)
- (BOOL)isSimulator;
#end
#implementation UIDevice (Additions)
- (BOOL)isSimulator {
if([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
return [NSProcessInfo processInfo].environment[#"SIMULATOR_DEVICE_NAME"] != nil;
} else {
return [[self model] isEqualToString:#"iPhone Simulator"];
}
}
#end
Let me clarify some things here:
TARGET_OS_SIMULATOR is not set in Swift code in many cases; you may be accidentally getting it imported due to a bridging header but this is brittle and not supported. It also isn't even possible in frameworks. This is why some people are confused about whether this works in Swift.
I strongly advise against using architecture as a substitute for simulator.
To perform dynamic checks:
Checking ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil is perfectly fine.
You can also get the underlying model being simulated by checking SIMULATOR_MODEL_IDENTIFIER which will return strings like iPhone10,3.
To perform static checks:
Xcode 9.2 & earlier: define your own Swift compilation flag (as shown in other answers).
Xcode 9.3+ use the new targetEnvironment condition:
#if targetEnvironment(simulator)
// for sim only
#else
// for device
#endif
Swift 5.2.4
Xcode 11.7
#if targetEnvironment(simulator)
#endif
Runtime, but simpler than most of the other solutions here:
if TARGET_OS_SIMULATOR != 0 {
// target is current running in the simulator
}
Alternatively, you can just call an Objective-C helper function that returns a boolean that uses the preprocessor macro (especially if you're already mixing in your project).
Edit: Not the best solution, especially as of Xcode 9.3. See HotJard's answer
What works for me since Swift 1.0 is checking for an architecture other than arm:
#if arch(i386) || arch(x86_64)
//simulator
#else
//device
#endif
I hope this extension comes handy.
extension UIDevice {
static var isSimulator: Bool = {
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}()
}
Usage:
if UIDevice.isSimulator {
print("running on simulator")
}
In modern systems:
#if targetEnvironment(simulator)
// sim
#else
// device
#endif
It's dat easy.
TARGET_IPHONE_SIMULATOR is deprecated in iOS 9. TARGET_OS_SIMULATOR is the replacement. Also TARGET_OS_EMBEDDED is available.
From TargetConditionals.h :
#if defined(__GNUC__) && ( defined(__APPLE_CPP__) || defined(__APPLE_CC__) || defined(__MACOS_CLASSIC__) )
. . .
#define TARGET_OS_SIMULATOR 0
#define TARGET_OS_EMBEDDED 1
#define TARGET_IPHONE_SIMULATOR TARGET_OS_SIMULATOR /* deprecated */
#define TARGET_OS_NANO TARGET_OS_WATCH /* deprecated */
Xcode 11, Swift 5
#if !targetEnvironment(macCatalyst)
#if targetEnvironment(simulator)
true
#else
false
#endif
#endif
In Xcode 7.2 (and earlier but I haven't tested how much earlier), you can set a platform specific build flag "-D TARGET_IPHONE_SIMULATOR" for "Any iOS Simulator".
Look in the project build settings under "Swift Compiler - Customer Flags" and then set the flag in "Other Swift Flags". You can set a platform specific flag by clicking the 'plus' icon when you hover over a build configuration.
There are a couple of advantages of doing it this way: 1) You can use the same conditional test ("#if TARGET_IPHONE_SIMULATOR") in your Swift and Objective-C code. 2) You can compile out variables that only apply to each build.
Xcode build settings screenshot
All described here Darwin.TargetConditionals:
https://github.com/apple/swift-corelibs-foundation/blob/master/CoreFoundation/Base.subproj/SwiftRuntime/TargetConditionals.h
TARGET_OS_SIMULATOR - Generated code will run under a simulator
Use this below code:
#if targetEnvironment(simulator)
// Simulator
#else
// Device
#endif
Works for Swift 4 and Xcode 9.4.1
Here is an Xcode 11 Swift example based off of HotJard's awesome answer above, this also adds an isDevice Bool and uses SIMULATOR_UDID instead of name. Variable assignments are done on each line so that you can more easily examine them in the debugger if you choose to.
import Foundation
// Extensions to UIDevice based on ProcessInfo.processInfo.environment keys
// to determine if the app is running on an actual device or the Simulator.
#objc extension UIDevice {
static var isSimulator: Bool {
let environment = ProcessInfo.processInfo.environment
let isSimulator = environment["SIMULATOR_UDID"] != nil
return isSimulator
}
static var isDevice: Bool {
let environment = ProcessInfo.processInfo.environment
let isDevice = environment["SIMULATOR_UDID"] == nil
return isDevice
}
}
There also is the dictionary entry of DTPlatformName which should contain simulator.
In addition to other answers.
In Objective-c, Just make sure you included TargetConditionals.
#include <TargetConditionals.h>
before using TARGET_OS_SIMULATOR.
I used this below code in Swift 3
if TARGET_IPHONE_SIMULATOR == 1 {
//simulator
} else {
//device
}
Swift 4:
Currently, I prefer to use the ProcessInfo class to know if the device is a simulator and which kind of device is in use:
if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] {
print("yes is a simulator :\(simModelCode)")
}
But, as you know, simModelCode isn't a comfortable code to understand immediatly which kind of simulator was launched so, if your need, you can try to see this other SO answer to determine the current iPhone/device model and to have a more human readable string.
I do not know if this will be useful to anyone, but at least the current version of M1 macs do not seem to pass the SIMULATOR_MODEL_IDENTIFIER into NSProcessInfo
I used
BOOL isMobile = [[NSProcessInfo processInfo].environment[#"USER"] isEqual:#"mobile"];
and swift equivalent. This may be fragile but it works.

Check if a function is available in Swift?

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

How to detect if app is being built for device or simulator in Swift

In Objective-C we can know if an app is being built for device or simulator using macros:
#if TARGET_IPHONE_SIMULATOR
// Simulator
#else
// Device
#endif
These are compile time macros and not available at runtime.
How can I achieve the same in Swift?
Update 30/01/19
While this answer may work, the recommended solution for a static check (as clarified by several Apple engineers) is to define a custom compiler flag targeting iOS Simulators. For detailed instructions on how to do to it, see #mbelsky's answer.
Original answer
If you need a static check (e.g. not a runtime if/else) you can't detect the simulator directly, but you can detect iOS on a desktop architecture like follows
#if (arch(i386) || arch(x86_64)) && os(iOS)
...
#endif
After Swift 4.1 version
Latest use, now directly for all in one condition for all types of simulators need to apply only one condition -
#if targetEnvironment(simulator)
// your simulator code
#else
// your real device code
#endif
For more clarification, you can check Swift proposal SE-0190
For older version -
Clearly, this is false on a device, but it returns true for the iOS Simulator, as specified in the documentation:
The arch(i386) build configuration returns true when the code is compiled for the 32–bit iOS simulator.
If you are developing for a simulator other than iOS, you can simply vary the os parameter: e.g.
Detect the watchOS simulator
#if (arch(i386) || arch(x86_64)) && os(watchOS)
...
#endif
Detect the tvOS simulator
#if (arch(i386) || arch(x86_64)) && os(tvOS)
...
#endif
Or, even, detect any simulator
#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(watchOS) || os(tvOS))
...
#endif
If you instead are ok with a runtime check, you can inspect the TARGET_OS_SIMULATOR variable (or TARGET_IPHONE_SIMULATOR in iOS 8 and below), which is truthy on a simulator.
Please notice that this is different and slightly more limited than using a preprocessor flag. For instance you won't be able to use it in place where a if/else is syntactically invalid (e.g. outside of functions scopes).
Say, for example, that you want to have different imports on the device and on the simulator. This is impossible with a dynamic check, whereas it's trivial with a static check.
#if (arch(i386) || arch(x86_64)) && os(iOS)
import Foo
#else
import Bar
#endif
Also, since the flag is replaced with a 0 or a 1 by the swift preprocessor, if you directly use it in a if/else expression the compiler will raise a warning about unreachable code.
In order to work around this warning, see one of the other answers.
OUTDATED FOR SWIFT 4.1. Use #if targetEnvironment(simulator) instead. Source
To detect simulator in Swift you can use build configuration:
Define this configuration -D IOS_SIMULATOR in Swift Compiler - Custom Flags > Other Swift Flags
Select Any iOS Simulator SDK in this drop down
Now you could use this statement to detect simulator:
#if IOS_SIMULATOR
print("It's an iOS Simulator")
#else
print("It's a device")
#endif
Also you could extend UIDevice class:
extension UIDevice {
var isSimulator: Bool {
#if IOS_SIMULATOR
return true
#else
return false
#endif
}
}
// Example of usage: UIDevice.current.isSimulator
Updated Info as of February 20, 2018
It looks like #russbishop has an authoritative answer that renders this answer "incorrect" - even though it appeared to work for a long time.
Detect if app is being built for device or simulator in Swift
Previous Answer
Based on #WZW's answer and #Pang's comments, I created a simple utility struct. This solution avoids warning produced by #WZW's answer.
import Foundation
struct Platform {
static var isSimulator: Bool {
return TARGET_OS_SIMULATOR != 0
}
}
Example usage:
if Platform.isSimulator {
print("Running on Simulator")
}
Swift 4
You can now use targetEnvironment(simulator) as an argument.
#if targetEnvironment(simulator)
// Simulator
#else
// Device
#endif
Updated for Xcode 9.3
From Xcode 9.3
#if targetEnvironment(simulator)
Swift supports a new platform condition targetEnvironment with a
single valid argument simulator. Conditional compilation of the form
'#if targetEnvironment(simulator)' can now be used to detect when the build target is a simulator. The Swift compiler will attempt to
detect, warn, and suggest the use of targetEnvironment(simulator) when
evaluating platform conditions that appear to be testing for simulator
environments indirectly, via the existing os() and arch() platform
conditions. (SE-0190)
iOS 9+:
extension UIDevice {
static var isSimulator: Bool {
return NSProcessInfo.processInfo().environment["SIMULATOR_DEVICE_NAME"] != nil
}
}
Swift 3:
extension UIDevice {
static var isSimulator: Bool {
return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
}
}
Before iOS 9:
extension UIDevice {
static var isSimulator: Bool {
return UIDevice.currentDevice().model == "iPhone Simulator"
}
}
Objective-C:
#interface UIDevice (Additions)
- (BOOL)isSimulator;
#end
#implementation UIDevice (Additions)
- (BOOL)isSimulator {
if([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
return [NSProcessInfo processInfo].environment[#"SIMULATOR_DEVICE_NAME"] != nil;
} else {
return [[self model] isEqualToString:#"iPhone Simulator"];
}
}
#end
Let me clarify some things here:
TARGET_OS_SIMULATOR is not set in Swift code in many cases; you may be accidentally getting it imported due to a bridging header but this is brittle and not supported. It also isn't even possible in frameworks. This is why some people are confused about whether this works in Swift.
I strongly advise against using architecture as a substitute for simulator.
To perform dynamic checks:
Checking ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil is perfectly fine.
You can also get the underlying model being simulated by checking SIMULATOR_MODEL_IDENTIFIER which will return strings like iPhone10,3.
To perform static checks:
Xcode 9.2 & earlier: define your own Swift compilation flag (as shown in other answers).
Xcode 9.3+ use the new targetEnvironment condition:
#if targetEnvironment(simulator)
// for sim only
#else
// for device
#endif
Swift 5.2.4
Xcode 11.7
#if targetEnvironment(simulator)
#endif
Runtime, but simpler than most of the other solutions here:
if TARGET_OS_SIMULATOR != 0 {
// target is current running in the simulator
}
Alternatively, you can just call an Objective-C helper function that returns a boolean that uses the preprocessor macro (especially if you're already mixing in your project).
Edit: Not the best solution, especially as of Xcode 9.3. See HotJard's answer
What works for me since Swift 1.0 is checking for an architecture other than arm:
#if arch(i386) || arch(x86_64)
//simulator
#else
//device
#endif
I hope this extension comes handy.
extension UIDevice {
static var isSimulator: Bool = {
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}()
}
Usage:
if UIDevice.isSimulator {
print("running on simulator")
}
In modern systems:
#if targetEnvironment(simulator)
// sim
#else
// device
#endif
It's dat easy.
TARGET_IPHONE_SIMULATOR is deprecated in iOS 9. TARGET_OS_SIMULATOR is the replacement. Also TARGET_OS_EMBEDDED is available.
From TargetConditionals.h :
#if defined(__GNUC__) && ( defined(__APPLE_CPP__) || defined(__APPLE_CC__) || defined(__MACOS_CLASSIC__) )
. . .
#define TARGET_OS_SIMULATOR 0
#define TARGET_OS_EMBEDDED 1
#define TARGET_IPHONE_SIMULATOR TARGET_OS_SIMULATOR /* deprecated */
#define TARGET_OS_NANO TARGET_OS_WATCH /* deprecated */
Xcode 11, Swift 5
#if !targetEnvironment(macCatalyst)
#if targetEnvironment(simulator)
true
#else
false
#endif
#endif
In Xcode 7.2 (and earlier but I haven't tested how much earlier), you can set a platform specific build flag "-D TARGET_IPHONE_SIMULATOR" for "Any iOS Simulator".
Look in the project build settings under "Swift Compiler - Customer Flags" and then set the flag in "Other Swift Flags". You can set a platform specific flag by clicking the 'plus' icon when you hover over a build configuration.
There are a couple of advantages of doing it this way: 1) You can use the same conditional test ("#if TARGET_IPHONE_SIMULATOR") in your Swift and Objective-C code. 2) You can compile out variables that only apply to each build.
Xcode build settings screenshot
All described here Darwin.TargetConditionals:
https://github.com/apple/swift-corelibs-foundation/blob/master/CoreFoundation/Base.subproj/SwiftRuntime/TargetConditionals.h
TARGET_OS_SIMULATOR - Generated code will run under a simulator
Use this below code:
#if targetEnvironment(simulator)
// Simulator
#else
// Device
#endif
Works for Swift 4 and Xcode 9.4.1
Here is an Xcode 11 Swift example based off of HotJard's awesome answer above, this also adds an isDevice Bool and uses SIMULATOR_UDID instead of name. Variable assignments are done on each line so that you can more easily examine them in the debugger if you choose to.
import Foundation
// Extensions to UIDevice based on ProcessInfo.processInfo.environment keys
// to determine if the app is running on an actual device or the Simulator.
#objc extension UIDevice {
static var isSimulator: Bool {
let environment = ProcessInfo.processInfo.environment
let isSimulator = environment["SIMULATOR_UDID"] != nil
return isSimulator
}
static var isDevice: Bool {
let environment = ProcessInfo.processInfo.environment
let isDevice = environment["SIMULATOR_UDID"] == nil
return isDevice
}
}
There also is the dictionary entry of DTPlatformName which should contain simulator.
In addition to other answers.
In Objective-c, Just make sure you included TargetConditionals.
#include <TargetConditionals.h>
before using TARGET_OS_SIMULATOR.
I used this below code in Swift 3
if TARGET_IPHONE_SIMULATOR == 1 {
//simulator
} else {
//device
}
Swift 4:
Currently, I prefer to use the ProcessInfo class to know if the device is a simulator and which kind of device is in use:
if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] {
print("yes is a simulator :\(simModelCode)")
}
But, as you know, simModelCode isn't a comfortable code to understand immediatly which kind of simulator was launched so, if your need, you can try to see this other SO answer to determine the current iPhone/device model and to have a more human readable string.
I do not know if this will be useful to anyone, but at least the current version of M1 macs do not seem to pass the SIMULATOR_MODEL_IDENTIFIER into NSProcessInfo
I used
BOOL isMobile = [[NSProcessInfo processInfo].environment[#"USER"] isEqual:#"mobile"];
and swift equivalent. This may be fragile but it works.

Resources