UI_USER_INTERFACE_IDIOM() crashes app distributed on devices ONLY - ios

I created a sample single page swift language based iOS app that CRASHES on this func call in viewDidLoad() -
func regularFont() -> UIFont {
var fontSize : CGFloat = (UI_USER_INTERFACE_IDIOM() == .Pad) ? 15 : 12
return UIFont.systemFontOfSize(fontSize)
}
But once I replace this UI_USER_INTERFACE_IDIOM() with Apple recommended UIDevice() method, it WORKS fine.
func regularFont() -> UIFont {
var fontSize : CGFloat = (UIDevice().userInterfaceIdiom == .Pad) ? 15 : 12
return UIFont.systemFontOfSize(fontSize)
}
The crash occurs on all devices I tested - iPhone 5s, iPhone 6 and iPad Air (all on iOS 8.x) and it happens ONLY on devices, NOT simulator.
PS: The app is installed on all devices through our OTA/web link.
To my surprise, we have another objective c language based app on Apple's App Store that uses UI_USER_INTERFACE_IDIOM() heavily, regularly updated... but never crashed due to this.
Any thoughts?

UI_USER_INTERFACE_IDIOM() is just an Objective-C macro, which is defined as:
#define UI_USER_INTERFACE_IDIOM() \ ([[UIDevice currentDevice] respondsToSelector:#selector(userInterfaceIdiom)] ? \ [[UIDevice currentDevice] userInterfaceIdiom] : \ UIUserInterfaceIdiomPhone)
So when you work with Swift, you need to use as:
UIDevice.currentDevice().userInterfaceIdiom == .Pad
UIDevice.currentDevice().userInterfaceIdiom == .Phone
UIDevice.currentDevice().userInterfaceIdiom == .Unspecified
Hope this could help.

I got it (partially!). Actually "release" implementation of UI_USER_INTERFACE_IDIOM() in swift project crashes the app.
Once I edit the scheme to "release" (Xcode > Product > Scheme > Edit Scheme > Run > Build Configuration change to "Release") and then run on simulator/device, the app crashes everywhere.. all devices/simulators/developer/distribution profiles.
However, still I have no clue why our app store app (objective c language based) does NOT crash.
My only guess is that it's a glitch in UI_USER_INTERFACE_IDIOM() API implementation with some language specific coding (swift vs objective c) by Apple.
Anyways, I would replace all UI_USER_INTERFACE_IDIOM() with UIDevice(). userInterfaceIdiom. I hope this helps someone!

Swift 5.3 and up:
if UIDevice.current.userInterfaceIdiom != .pad {
// do stuff
}

Related

Detecting if iPhone or iPad without opting for Universal App

I really want this to work:
if UIDevice.current.userInterfaceIdiom == .pad {
print("iPad")
} else {
print("not iPad")
}
However, my app only prints "not iPad" even though I am using an iPad. I have Devices (under Deployment Info) set to iPhone. If I change this to Universal it works, but I don't want a universal app, I just want to be able to detect if an iPhone or iPad is being used (even though the app is for iPhones, due to compatibility mode it still can be run on iPads).
So how can I detect if the device is an iPad or iPhone without changing my app to Universal? Thanks
You can check the model:
if UIDevice.current.model.hasPrefix("iPad") {
print("it is an iPad")
} else {
print("it is not an iPad")
}
One thing you can do is get the inner-screen width of the page. Phones are generally below 786 px and you can call everything else an iPad. Use can do something like this
var width = window.innerWidth;
If (width > 786px) {
print(‘ipad’);
} else {
print(‘not ipad’)
}
iPhone Only app can be downloaded to iPad. But in current scenario, we doesn't have device that's much smaller resolution(deployment target: 9).
OBJ-C
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && [[[UIDevice currentDevice] model] hasPrefix:#"iPad"]) {
// This app is an iPhone app running on an iPad
NSLog(#"This app is an iPhone app running on an iPad");
}
Swift
if UIDevice.current.userInterfaceIdiom == .phone, UIDevice.current.model.hasPrefix("iPad") {
print("iPad")
}
Try this,
print("Model - \(UIDevice.current.model)")

supportedInterfaceOrientations called on all iPhones except 6+

On my iPhone app I have it restricted to portrait only under the project targets deployment info
There is one page that I want only in landscape and I use the supportedInterfaceOrientations method to obtain that.
Standard implementation:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Landscape
}
It works perfectly on all iPhone devices and iOS version except for iPhone 6+. The supportedInterfaceOrientations method is never called.
I cant find any reason why this might be affecting just the iPhone 6+, any tips would be greatly apreciated.
you can try this code it works for me
// you have to import foundation
import Foundation
class YourViewController : UIViewController {
var device = self.platform()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
if device.lowercaseString.rangeOfString("iphone6plus") != nil {
supportedInterfaceOrientations()
}
}
// add this method to your view controller
func platform() -> String {
var sysinfo = utsname()
uname(&sysinfo) // ignore return value
return NSString(bytes: &sysinfo.machine, length: Int(_SYS_NAMELEN), encoding:
NSASCIIStringEncoding)! as String
}
Please note that this will not run on the simulator but will run perfectly on the actual device.
Looking at this question, try this code snippet:
- (NSUInteger) supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
// iPhone 5S and below: 320x480
// iPhone 6: 375x667
// iPhone 6 Plus: 414x736
CGSize screenSize = [UIScreen mainScreen].bounds.size;
// The way how UIScreen reports its bounds has changed in iOS 8.
// Using MIN() and MAX() makes this code work for all iOS versions.
CGFloat smallerDimension = MIN(screenSize.width, screenSize.height);
CGFloat largerDimension = MAX(screenSize.width, screenSize.height);
if (smallerDimension >= 400 && largerDimension >= 700)
return UIInterfaceOrientationMask.Landscape;
else
return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
}
else
{
// Don't need to examine screen dimensions on iPad
return UIInterfaceOrientationMask.Landscape;
}
}
-Herzbube
Lacking an official Apple API, this is the workaround that I've come up with:
[Code]
The snippet simply assumes that a screen with dimensions above a semi-arbitrarily chosen size is suitable for rotation. Semi-arbitrarily, because a threshold of 400x700 includes the iPhone 6 Plus, but excludes the iPhone 6.
Although this solution is rather simple, I like it exactly because of its lack of sophistication. I don't really need to distinguish exactly between devices, so any clever solutions such as the one in Jef's answer are overkill for my purposes.
All I did was change the 1st and 3rd return values from UIInterfaceOrientationMaskAll to UIInterfaceOrientationMask.Landscape.

How to tell which device I'm on in Xcode UI Testing?

While an Xcode UI Test is running, I want to know which device/environment is being used (e.g. iPad Air 2, iOS 9.0, Simulator).
How can I get this information?
Using Swift 3 (change .pad to .phone as necessary):
if UIDevice.current.userInterfaceIdiom == .pad {
// Ipad specific checks
}
Using older versions of Swift:
UIDevice.currentDevice().userInterfaceIdiom
Unfortunately there is no direct way of querying the current device. However you can work around by querying the size classes of the device:
private func isIpad(app: XCUIApplication) -> Bool {
return app.windows.elementBoundByIndex(0).horizontalSizeClass == .Regular && app.windows.elementBoundByIndex(0).verticalSizeClass == .Regular
}
As you can see in the Apple Description of size classes, only iPad devices (currently) have both vertical and horizontal size class "Regular".
You can check using the windows element frame XCUIApplication().windows.element(boundBy: 0).frame and check the device type.
You can also set an extension for XCUIDevice with a currentDevice property:
/// Device types
public enum Devices: CGFloat {
/// iPhone
case iPhone4 = 480
case iPhone5 = 568
case iPhone7 = 667
case iPhone7Plus = 736
/// iPad - Portraite
case iPad = 1024
case iPadPro = 1366
/// iPad - Landscape
case iPad_Landscape = 768
case iPadPro_Landscape = 0
}
/// Check current device
extension XCUIDevice {
public static var currentDevice:Devices {
get {
let orientation = XCUIDevice.shared().orientation
let frame = XCUIApplication().windows.element(boundBy: 0).frame
switch orientation {
case .landscapeLeft, .landscapeRight:
return frame.width == 1024 ? .iPadPro_Landscape : Devices(rawValue: frame.width)!
default:
return Devices(rawValue: frame.height)!
}
}
}
}
Usage
let currentDevice = XCUIDevice.currentDevice
Maybe someone would be come in handy the same for XCTest on Objective C:
// Check if the device is iPhone
if ( ([[app windows] elementBoundByIndex:0].horizontalSizeClass != XCUIUserInterfaceSizeClassRegular) || ([[app windows] elementBoundByIndex:0].verticalSizeClass != XCUIUserInterfaceSizeClassRegular) ) {
// do something for iPhone
}
else {
// do something for iPad
}
Swift: 5.2.4
Xcode: 11.6
var isiPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
With iOS13+, you can now use UITraitCollection.current to get the complete set of traits for the current environment. (This is the "iOS interface environment for your app, including traits such as horizontal and vertical size class, display scale, and user interface idiom." doc)
In your case, you can access its property .userInterfaceIdiom to check for one of the device types in the UIUserInterfaceIdiom enumeration.
As an aside, if you just want to get the horizontal/vertical size classes of the trait collection, you can be more backwards compatible with your tests (Xcode 10.0+) just by accessing myXCUIElement.horizontalSizeClass and .verticalSizeClass within your test, as they are exposed via the XCUIElementAttributes protocol that all UI elements adopt. (Note though that I was getting .unspecified when calling off of XCUIApplication(); best to use a real UI element in a window. If you don't have one on hand, you can always still use something like app!.windows.element(boundBy: 0).horizontalSizeClass == .regular as mentioned in the past.)

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.

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