My iOS app uses a custom height for the UINavigationBar
which leads to some problems on the new iPhone X.
Does someone already know how to reliable detect programmatically (in Objective-C) if an app is running on iPhone X?
EDIT:
Of course checking the size of the screen is possible, however, I wonder if there is some "build in" method like TARGET_OS_IPHONE
to detect iOS...
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
if (screenSize.height == 812)
NSLog(@"iPhone X");
}
EDIT 2:
I do not think, that my question is a duplicate of the linked question. Of course, there are methods to "measure" different properties of the current device and to use the results to decide which device is used. However, this was not the actual point of my question as I tried to emphasize in my first edit.
The actual question is: "Is it possible to directly detect if the current device is an iPhone X (e.g. by some SDK feature) or do I have to use indirect measurements"?
By the answers given so far, I assume that the answer is "No, there is no direct methods. Measurements are the way to go".
Based on your question, the answer is no. There are no direct methods. For more information you can get the information here:
How to get device make and model on iOS?
and
how to check screen size of iphone 4 and iphone 5 programmatically in swift
The iPhone X height is 2436 px
From Device Screen Sizes and resolutions:
https://i.stack.imgur.com/JcCJp.png
From Device Screen Sizes and Orientations:
https://i.stack.imgur.com/d0x04.png
Swift 3 and later:
if UIDevice().userInterfaceIdiom == .phone {
switch UIScreen.main.nativeBounds.height {
case 1136:
print("iPhone 5 or 5S or 5C")
case 1334:
print("iPhone 6/6S/7/8")
case 1920, 2208:
print("iPhone 6+/6S+/7+/8+")
case 2436:
print("iPhone X/XS/11 Pro")
case 2688:
print("iPhone XS Max/11 Pro Max")
case 1792:
print("iPhone XR/ 11 ")
default:
print("Unknown")
}
}
Objective-C:
if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
switch ((int)[[UIScreen mainScreen] nativeBounds].size.height) {
case 1136:
printf("iPhone 5 or 5S or 5C");
break;
case 1334:
printf("iPhone 6/6S/7/8");
break;
case 1920:
case 2208:
printf("iPhone 6+/6S+/7+/8+");
break;
case 2436:
printf("iPhone X/XS/11 Pro");
break;
case 2688:
printf("iPhone XS Max/11 Pro Max");
break;
case 1792:
printf("iPhone XR/ 11 ");
break;
default:
printf("Unknown");
break;
}
}
Xamarin.iOS:
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone) {
if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 1136) {
Console.WriteLine("iPhone 5 or 5S or 5C");
} else if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 1334) {
Console.WriteLine("iPhone 6/6S/7/8");
} else if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 1920 || (UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 2208) {
Console.WriteLine("iPhone 6+/6S+/7+/8+");
} else if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 2436) {
Console.WriteLine("iPhone X, XS, 11 Pro");
} else if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 2688) {
Console.WriteLine("iPhone XS Max, 11 Pro Max");
} else if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 1792) {
Console.WriteLine("iPhone XR, 11");
} else {
Console.WriteLine("Unknown");
}
}
Based on your question as follow:
Or use screenSize.height
as float 812.0f
not int 812
.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
// 812.0 on iPhone X, XS
// 896.0 on iPhone XS Max, XR.
if (screenSize.height >= 812.0f)
NSLog(@"iPhone X");
}
For more information you can refer the following page in iOS Human Interface Guidelines:
Adaptivity and Layout - Visual Design - iOS - Human Interface Guidelines
Swift:
Detect with topNotch
:
If anyone considering using notch to detect iPhoneX, mind that on "landscape" its same for all iPhones.
var hasTopNotch: Bool {
if #available(iOS 13.0, *) {
return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.safeAreaInsets.top ?? 0 > 20
}else{
return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 20
}
return false
}
Objective-C:
- (BOOL)hasTopNotch {
if (@available(iOS 13.0, *)) {
return [self keyWindow].safeAreaInsets.top > 20.0;
}else{
return [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.top > 20.0;
}
return NO;
}
- (UIWindow*)keyWindow {
UIWindow *foundWindow = nil;
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
foundWindow = window;
break;
}
}
return foundWindow;
}
UPDATE:
Do not use the userInterfaceIdiom
property to identify the device type, as the documentation for userInterfaceIdiom explains:
For universal applications, you can use this property to tailor the behavior of your application for a specific type of device. For example, iPhone and iPad devices have different screen sizes, so you might want to create different views and controls based on the type of the current device.
That is, this property is just used to identify the running app's view style. However, the iPhone app (not the universal) could be installed in iPad device via App store, in that case, the userInterfaceIdiom
will return the UIUserInterfaceIdiomPhone
, too.
The right way is to get the machine name via uname
. Check the following for details:
How to get device make and model on iOS?
Another possibility, which works on iOS 11 and iOS 12 because the iPhone X is the only one with a notch at the top and an inset of 44. That is what I am really detecting here:
Objective-C:
BOOL iPhoneX = NO;
if (@available(iOS 11.0, *)) {
UIWindow *mainWindow = [[[UIApplication sharedApplication] delegate] window];
if (mainWindow.safeAreaInsets.top > 24.0) {
iPhoneX = YES;
}
}
Swift 4:
/// Has safe area
///
/// with notch: 44.0 on iPhone X, XS, XS Max, XR.
///
/// without notch: 20.0 on iPhone 8 on iOS 12+.
///
static var hasSafeArea: Bool {
guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top, topPadding > 24 else {
return false
}
return true
}
And of course, you might need to check the left and right safe area insets if you are in landscape orientation.
Edit: _window is the UIWindow of the AppDelegate, where this check is done in application didFinishLaunchingWithOptions.
Answer updated for iOS 12 to check if top > 24 rather than top > 0.
Edit: In the simulator you can go to Hardware, Toggle In-call Status Bar. Doing that shows me that the status bar height does not change on iPhone X on iOS 11 or iPhone XS iOS 12 when engaged in a call. All that changes is the time icon, which gets a green background, in both cases. Here's a snap:
https://i.stack.imgur.com/ZVVzv.png
if _window.safeAreaInsets != UIEdgeInsets.zero
to allow for any device orientation
.top
, safeAreaInsets.bottom
will be 34 on iPhone X and 0 on other devices
You shall perform different detections of iPhone X depending on the actual need.
for dealing with the top notch (statusbar, navbar), etc.
class var hasTopNotch: Bool {
if #available(iOS 11.0, tvOS 11.0, *) {
// with notch: 44.0 on iPhone X, XS, XS Max, XR.
// without notch: 24.0 on iPad Pro 12.9" 3rd generation, 20.0 on iPhone 8 on iOS 12+.
return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 24
}
return false
}
for dealing with the bottom home indicator (tabbar), etc.
class var hasBottomSafeAreaInsets: Bool {
if #available(iOS 11.0, tvOS 11.0, *) {
// with home indicator: 34.0 on iPhone X, XS, XS Max, XR.
// with home indicator: 20.0 on iPad Pro 12.9" 3rd generation.
return UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0 > 0
}
return false
}
for backgrounds size, fullscreen features, etc.
class var isIphoneXOrBigger: Bool {
// 812.0 on iPhone X, XS.
// 896.0 on iPhone XS Max, XR.
return UIScreen.main.bounds.height >= 812
}
Note: eventually mix it with UIDevice.current.userInterfaceIdiom == .phone
Note: this method requires to have a LaunchScreen storyboard or proper LaunchImages
for backgrounds ratio, scrolling features, etc.
class var isIphoneXOrLonger: Bool {
// 812.0 / 375.0 on iPhone X, XS.
// 896.0 / 414.0 on iPhone XS Max, XR.
return UIScreen.main.bounds.height / UIScreen.main.bounds.width >= 896.0 / 414.0
}
Note: this method requires to have a LaunchScreen storyboard or proper LaunchImages
for analytics, stats, tracking, etc.
Get the machine identifier and compare it to documented values:
class var isIphoneX: Bool {
var size = 0
sysctlbyname("hw.machine", nil, &size, nil, 0)
var machine = [CChar](repeating: 0, count: size)
sysctlbyname("hw.machine", &machine, &size, nil, 0)
let model = String(cString: machine)
return model == "iPhone10,3" || model == "iPhone10,6"
}
To include the simulator as a valid iPhone X in your analytics:
class var isIphoneX: Bool {
let model: String
if TARGET_OS_SIMULATOR != 0 {
model = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] ?? ""
} else {
var size = 0
sysctlbyname("hw.machine", nil, &size, nil, 0)
var machine = [CChar](repeating: 0, count: size)
sysctlbyname("hw.machine", &machine, &size, nil, 0)
model = String(cString: machine)
}
return model == "iPhone10,3" || model == "iPhone10,6"
}
To include iPhone XS, XS Max and XR, simply look for models starting with "iPhone11,":
return model == "iPhone10,3" || model == "iPhone10,6" || model.starts(with: "iPhone11,")
for faceID support
import LocalAuthentication
/// will fail if user denies canEvaluatePolicy(_:error:)
class var canUseFaceID: Bool {
if #available(iOS 11.0, *) {
return LAContext().biometryType == .typeFaceID
}
return false
}
return LAContext().biometryType == .typeFaceID
would work even if the user had denied canEvaluatePolicy, but it doesn't work for me, it still returns .none
model == "iPhone10,3" || model == "iPhone10,6"
), and if canUseFaceID
returns false, then it means it was denied by user.
You can do like this to detect iPhone X device according to dimension.
Swift
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
//iPhone X
}
Objective - C
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && UIScreen.mainScreen.nativeBounds.size.height == 2436) {
//iPhone X
}
https://i.stack.imgur.com/xX17z.png
But,
This is not sufficient way. What if Apple announced next iPhone with same dimension of iPhone X. so the best way is to use Hardware string to detect the device.
For newer device Hardware string is as below.
iPhone 8 - iPhone10,1 or iPhone 10,4
iPhone 8 Plus - iPhone10,2 or iPhone 10,5
iPhone X - iPhone10,3 or iPhone10,6
[UIDevice currentDevice]
instead of [[UIDevice alloc] init]
Check out the device model / machine name, DO NOT use the point/pixel count in your code directly, it's hard code and meaningless for the device hardware, the device model is the only unique identifier for a type of device to match.
#import <sys/utsname.h>
NSString* deviceName()
{
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine
encoding:NSUTF8StringEncoding];
}
Result:
@"iPhone10,3" on iPhone X (CDMA)
@"iPhone10,6" on iPhone X (GSM)
Refer to this answer.
Full code implementation:
#import <sys/utsname.h>
NSString * GetDeviceModel(void)
{
static dispatch_once_t onceToken;
static NSString *strModelID = nil;
dispatch_once(&onceToken, ^{
#if TARGET_IPHONE_SIMULATOR
strModelID = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"];
#else
struct utsname systemInfo;
uname(&systemInfo);
strModelID = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
#endif
});
return strModelID;
}
// See the `Hardware strings` in https://en.wikipedia.org/wiki/List_of_iOS_devices
BOOL IsiPhoneX(void)
{
NSString *strModelID = GetDeviceModel();
return [strModelID isEqualToString:@"iPhone10,3"] || [strModelID isEqualToString:@"iPhone10,6"];
}
BOOL IsNotchiPhone(void)
{
NSArray<NSString *> *notchiModels = @[
@"iPhone10,3", @"iPhone10,6", // iPhone X
@"iPhone11,2", @"iPhone11,4", @"iPhone11,6", // iPhone XS (Max)
@"iPhone11,8", // iPhone XR
@"iPhone12,1", @"iPhone12,3", @"iPhone12,5", // iPhone 11 (Pro (Max))
@"iPhone13,1", @"iPhone13,2", @"iPhone13,3", @"iPhone13,4", // iPhone 12 ([mini]|[Pro (Max)])
];
return [notchiModels containsObject:GetDeviceModel()];
}
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_4 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 480.0)
#define IS_IPHONE_5 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 568.0)
#define IS_IPHONE_6 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 667.0)
#define IS_IPHONE_6PLUS (IS_IPHONE && [[UIScreen mainScreen] nativeScale] == 3.0f)
#define IS_IPHONE_6_PLUS (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 736.0)
#define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0)
define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0)
#define IS_IPHONE_XS (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0)
#define IS_IPHONE_X_MAX (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 896.0)
#define IS_RETINA ([[UIScreen mainScreen] scale] >= 2.0) // 3.0 for iPhone X, 2.0 for others
#define IS_IPAD_DEVICE [(NSString*)[UIDevice currentDevice].model hasPrefix:@"iPad"]
Note:- Be careful, it works fine only for portrait orientation
nativeScale
with 3.0
, right?
After looking at all the answers this is what I ended up doing:
Solution (Swift 4.1 compatible)
extension UIDevice {
static var isIphoneX: Bool {
var modelIdentifier = ""
if isSimulator {
modelIdentifier = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] ?? ""
} else {
var size = 0
sysctlbyname("hw.machine", nil, &size, nil, 0)
var machine = [CChar](repeating: 0, count: size)
sysctlbyname("hw.machine", &machine, &size, nil, 0)
modelIdentifier = String(cString: machine)
}
return modelIdentifier == "iPhone10,3" || modelIdentifier == "iPhone10,6"
}
static var isSimulator: Bool {
return TARGET_OS_SIMULATOR != 0
}
}
Use
if UIDevice.isIphoneX {
// is iPhoneX
} else {
// is not iPhoneX
}
Note
Pre Swift 4.1 you can check if the app is running on a simulator like so:
TARGET_OS_SIMULATOR != 0
From Swift 4.1 and onwards you can check if the app is running on a simulator using the Target environment platform condition:
#if targetEnvironment(simulator)
return true
#else
return false
#endif
(the older method will still work, but this new method is more future proof)
All of these answers based on dimensions are susceptible to incorrect behavior on future devices. They'll work today, but what if there's an iPhone next year that's the same size but has the camera, etc. under the glass so there's no "notch?" If the only option is to update the app, then it's a poor solution for you and your customers.
You can also check the hardware model string like "iPhone10,1", but that's problematic because sometimes Apple releases different model numbers for different carriers around the world.
The correct approach is to redesign the top layout, or solve the problems you're having with the custom navigation bar height (that's what I'd focus on). But, if you decide not to do either of those things, realize that whatever you're doing is a hack to get this to work today, and you'll need to correct it at some point, perhaps multiple times, to keep the hacks working.
SWIFT 4/5 reusable extension with iPhone 12 support
extension UIDevice {
enum `Type` {
case iPhone_5_5S_5C_SE1
case iPhone_6_6S_7_8_SE2
case iPhone_6_6S_7_8_PLUS
case iPhone_X_XS_12mini
case iPhone_XR_11
case iPhone_XS_11Pro_Max
case iPhone_12_Pro
case iPhone_12_Pro_Max
}
var hasHomeButton: Bool {
switch type {
case . iPhone_X_XS_12mini, . iPhone_XR_11, .iPhone_XS_11Pro_Max, . iPhone_XS_11Pro_Max, .iPhone_12_Pro, .iPhone_12_Pro_Max:
return false
default:
return true
}
}
var type: Type {
if UI_USER_INTERFACE_IDIOM() == .phone {
switch UIScreen.main.nativeBounds.height {
case 1136:
return .iPhone_5_5S_5C_SE1
case 1334:
return .iPhone_6_6S_7_8_SE2
case 1920, 2208:
return .iPhone_6_6S_7_8_PLUS
case 2436:
return .iPhone_X_XS_12mini
case 2532:
return .iPhone_12_Pro
case 2688:
return .iPhone_XS_11Pro_Max
case 2778:
return .iPhone_12_Pro_Max
case 1792:
return .iPhone_XR_11
default:
assertionFailure("Unknown phone device detected!")
return .iPhone_6_6S_7_8_SE2
}
} else {
assertionFailure("Unknown idiom device detected!")
return .iPhone_6_6S_7_8_SE2
}
}
}
UIDevice.current.hasHomeButton
SWIFT 4+ Answer
iPhone X, XR, XS, XSMAX, 11 Pro, 11 Pro Max:
Note: Need real device for test
let deviceType = UIDevice.current.modelName
switch deviceType {
case "iPhone10,3", "iPhone10,6":
print("iPhoneX")
case "iPhone11,2":
print("iPhone XS")
case "iPhone11,4":
print("iPhone XS Max")
case "iPhone11,6":
print("iPhone XS Max China")
case "iPhone11,8":
print("iPhone XR")
case "iPhone12,3":
print("iPhone 11 Pro")
case "iPhone12,5":
print("iPhone 11 Pro Max")
default:
break
}
extension UIDevice {
var modelName: String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}
}
Yes, it is possible. Download the UIDevice-Hardware extension (or install via CocoaPod 'UIDevice-Hardware') and then use:
NSString* modelID = [[[UIDevice currentDevice] modelIdentifier];
BOOL isIphoneX = [modelID isEqualToString:@"iPhone10,3"] || [modelID isEqualToString:@"iPhone10,6"];
Note that this won't work in the Simulator, only on the actual device.
ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"]
in Simulator to get the actual values from Xcode.
I know it's only a Swift solution, but it could help someone.
I have globals.swift
in every project with some code to ease my life and one of the things I always add are ScreenSize
and hasNotch
to easily detect phone type and dimensions:
struct ScreenSize {
static let width = UIScreen.main.bounds.size.width
static let height = UIScreen.main.bounds.size.height
}
var hasNotch: Bool {
return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 0
}
Then to use it:
if hasNotch {
print("This executes on all phones with a notch")
}
if DeviceType.iPhoneX { //do something for iPhone X notch }else{ // don’t do anything about notch }
According the @saswanb's response, this is a Swift 4 version :
var iphoneX = false
if #available(iOS 11.0, *) {
if ((UIApplication.shared.keyWindow?.safeAreaInsets.top)! > CGFloat(0.0)) {
iphoneX = true
}
}
keyWindow
is nil
until the main view controller has called viewDidAppear
All the answers that are using the height
are only half part of the story for one reason. If you're going to check like that when device orientation is landscapeLeft
or landscapeRight
the check will fail, because the height
is swapped out with the width
.
That's why my solution looks like this in Swift 4.0:
extension UIScreen {
///
static var isPhoneX: Bool {
let screenSize = UIScreen.main.bounds.size
let width = screenSize.width
let height = screenSize.height
return min(width, height) == 375 && max(width, height) == 812
}
}
Do NOT use screen pixel size as other solutions have suggested, this is bad as it can result in false positives for future devices; will not work if UIWindow hasn't yet rendered (AppDelegate), won't work in landscape apps, and can fail on simulator if scale is set.
I've, instead, made a macro for this purpose, it's very easy to use and relies on hardware flags to prevent the aforementioned issues.
Edit: Updated to support iPhoneX, iPhone XS, iPhoneXR, iPhoneXS Max
To Use:
if (IS_DEVICE_IPHONEX) {
//do stuff
}
Yup, really.
Macro:
Just copy paste this anywhere, I prefer the very bottom of my .h file after @end
#import <sys/utsname.h>
#if TARGET_IPHONE_SIMULATOR
#define IS_SIMULATOR YES
#else
#define IS_SIMULATOR NO
#endif
#define IS_DEVICE_IPHONEX (\
(^BOOL (void){\
NSString *__modelIdentifier;\
if (IS_SIMULATOR) {\
__modelIdentifier = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"];\
} else {\
struct utsname __systemInfo;\
uname(&__systemInfo);\
__modelIdentifier = [NSString stringWithCString:__systemInfo.machine encoding:NSUTF8StringEncoding];\
}\
NSString *__iPhoneX_GSM_Identifier = @"iPhone10,6";\
NSString *__iPhoneX_CDMA_Identifier = @"iPhone10,3";\
NSString *__iPhoneXR_Identifier = @"iPhone11,8";\
NSString *__iPhoneXS_Identifier = @"iPhone11,2";\
NSString *__iPhoneXSMax_China_Identifier = @"iPhone11,6";\
NSString *__iPhoneXSMax_Other_Identifier = @"iPhone11,4";\
return ([__modelIdentifier isEqualToString:__iPhoneX_GSM_Identifier] || [__modelIdentifier isEqualToString:__iPhoneX_CDMA_Identifier] || [__modelIdentifier isEqualToString:__iPhoneXR_Identifier] || [__modelIdentifier isEqualToString:__iPhoneXS_Identifier] || [__modelIdentifier isEqualToString:__iPhoneXSMax_China_Identifier] || [__modelIdentifier isEqualToString:__iPhoneXSMax_Other_Identifier]);\
})()\
)
if (@available(iOS 11.0, *)) { [UIApplication sharedApplication].keyWindow.safeAreaInsets.top }
You should not assume that the only device that Apple releases with a different UINavigationBar height will be the iPhone X. Try to solve this problem using a more generic solution. If you want the bar to always be 20px bigger than its default height, your code should add 20px to the height of the bar, instead of setting it to 64px (44px + 20px).
struct ScreenSize {
static let width = UIScreen.main.bounds.size.width
static let height = UIScreen.main.bounds.size.height
static let maxLength = max(ScreenSize.width, ScreenSize.height)
static let minLength = min(ScreenSize.width, ScreenSize.height)
static let frame = CGRect(x: 0, y: 0, width: ScreenSize.width, height: ScreenSize.height)
}
struct DeviceType {
static let iPhone4orLess = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength < 568.0
static let iPhone5orSE = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 568.0
static let iPhone678 = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 667.0
static let iPhone678p = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 736.0
static let iPhoneX = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 812.0
static let IS_IPAD = UIDevice.current.userInterfaceIdiom == .pad && ScreenSize.maxLength == 1024.0
static let IS_IPAD_PRO = UIDevice.current.userInterfaceIdiom == .pad && ScreenSize.maxLength == 1366.0
}
Swift 3 + 4:
without need of any device size pixel value
//UIApplication+SafeArea.swift
extension UIApplication {
static var isDeviceWithSafeArea:Bool {
if #available(iOS 11.0, *) {
if let topPadding = shared.keyWindow?.safeAreaInsets.bottom,
topPadding > 0 {
return true
}
}
return false
}
}
Example:
if UIApplication.isDeviceWithSafeArea {
//e.g. change the frame size height of your UITabBar
}
In Portrait only I use the view's frame's width and height to check:
override func viewDidLoad() {
super.viewDidLoad()
// iPhone Xr: -414 x 896
// iPhone Xs Max: -414 x 896
// iPhone X, Xs: -375 x 812
if view.frame.width == 414 && view.frame.height == 896 || view.frame.width == 375 && view.frame.height == 812 {
print("iPhone X")
} else {
print("not iPhone X")
}
}
The portrait screen dimensions are listed here
https://i.stack.imgur.com/TmXS3.png
UPDATE
This answer is old and now that there are more X series in the iPhone lineup you would either have to list all of those dimensions inside the the if-else
or it would be much easier to just check to see if the device has a notch. I got this answer/code from somewhere on SO about 1.5 yrs ago. If I could link to the code I would.
// 1. add an extension to UIDevice with this computed property
extension UIDevice {
var hasTopNotch: Bool {
if #available(iOS 11.0, tvOS 11.0, *) {
return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 20
}
return false
}
}
// 2. to use in any class
override func viewDidLoad() {
super.viewDidLoad()
if UIDevice.current.hasTopNotch {
print("X series")
} else {
print("regular phone")
}
}
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0f)
- (BOOL)isIphoneX {
if (@available(iOS 11.0, *)) {
UIWindow *window = UIApplication.sharedApplication.keyWindow;
CGFloat topPadding = window.safeAreaInsets.top;
if(topPadding>0) {
return YES;
}
else {
return NO;
}
}
else {
return NO;
}
}
Usually, the Programmer needs it for constraining to top or bottom, so these methods can help
static func extraTop() -> CGFloat {
var top: CGFloat = 0
if #available(iOS 11.0, *) {
if let t = UIApplication.shared.keyWindow?.safeAreaInsets.top {
top = t
}
}
return top
}
static func extraBottom() -> CGFloat {
var bottom: CGFloat = 0
if #available(iOS 11.0, *) {
if let b = UIApplication.shared.keyWindow?.safeAreaInsets.bottom {
bottom = b
}
}
return bottom
}
For before iPhone X these methods return: 0
For iPhone X: 44 and 34 accordingly
Then just add these extras to top or bottom constraints
For those getting 2001px instead of 2436px for the native bounds height (like me), it is because you built your app with an older SDK, before iOS 11 (Xcode 8 instead of Xcode 9). With an older SDK, iOS will display the apps "black boxed" on the iPhone X instead of extending the screen edge-to-edge, beyond the top "sensor notch". This reduces the screen size which is why that property returns 2001 instead of 2436.
The simplest solution is to just check for both sizes if you are only interested in device detection. I used this method for detecting FaceID while building with an older Xcode SDK which doesn't have the ENUM value specifying the biometric type. In this situation, device detection using screen height seemed like the best way to know whether the device had FaceID vs TouchID without having to update Xcode.
I was using Peter Kreinz's code (because it was clean and did what I needed) but then I realized it works just when the device is on portrait (since top padding will be on top, obviously) So I created an extension to handle all the orientations with its respective paddings, without relaying on the screen size:
extension UIDevice {
var isIphoneX: Bool {
if #available(iOS 11.0, *), isIphone {
if isLandscape {
if let leftPadding = UIApplication.shared.keyWindow?.safeAreaInsets.left, leftPadding > 0 {
return true
}
if let rightPadding = UIApplication.shared.keyWindow?.safeAreaInsets.right, rightPadding > 0 {
return true
}
} else {
if let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top, topPadding > 0 {
return true
}
if let bottomPadding = UIApplication.shared.keyWindow?.safeAreaInsets.bottom, bottomPadding > 0 {
return true
}
}
}
return false
}
var isLandscape: Bool {
return UIDeviceOrientationIsLandscape(orientation) || UIInterfaceOrientationIsLandscape(UIApplication.shared.statusBarOrientation)
}
var isPortrait: Bool {
return UIDeviceOrientationIsPortrait(orientation) || UIInterfaceOrientationIsPortrait(UIApplication.shared.statusBarOrientation)
}
var isIphone: Bool {
return self.userInterfaceIdiom == .phone
}
var isIpad: Bool {
return self.userInterfaceIdiom == .pad
}
}
And on your call site you just:
let res = UIDevice.current.isIphoneX
I elaborated on your's on else's answers and made swift extension on UIDevice. I like swift enums and "everything in order" & atomized. I've created solution that works both on device & simulator.
Advantages: - simple interface, usage e.g. UIDevice.current.isIPhoneX
- UIDeviceModelType
enum gives you ability to easily extend model specific features and constants that you want to use in your app, e.g. cornerRadius
Disadvantage: - it's model specific solution, not resolution specific - e.g. if Apple will produce another model with the same specs, this won't work correctly and you need to add another model in order to make this work => you need to update your app.
extension UIDevice {
enum UIDeviceModelType : Equatable {
///iPhoneX
case iPhoneX
///Other models
case other(model: String)
static func type(from model: String) -> UIDeviceModelType {
switch model {
case "iPhone10,3", "iPhone10,6":
return .iPhoneX
default:
return .other(model: model)
}
}
static func ==(lhs: UIDeviceModelType, rhs: UIDeviceModelType) -> Bool {
switch (lhs, rhs) {
case (.iPhoneX, .iPhoneX):
return true
case (.other(let modelOne), .other(let modelTwo)):
return modelOne == modelTwo
default:
return false
}
}
}
var simulatorModel: String? {
guard TARGET_OS_SIMULATOR != 0 else {
return nil
}
return ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"]
}
var hardwareModel: String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let model = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return model
}
var modelType: UIDeviceModelType {
let model = self.simulatorModel ?? self.hardwareModel
return UIDeviceModelType.type(from: model)
}
var isIPhoneX: Bool {
return modelType == .iPhoneX
}
}
Mirror
, it will be faster to use sysctlbyname
as done in Cloud9999Strife answer (and in my answer too).
I rely on the Status Bar Frame height to detect if it's an iPhone X :
if UIApplication.shared.statusBarFrame.height >= CGFloat(44) {
// It is an iPhone X
}
This is for application un portrait. You could also check the size according to the device orientation. Also, on other iPhones, the Status Bar may be hidden, so the frame height is 0
. On iPhone X, the Status Bar is never hidden.
controller
with this: - (BOOL)prefersStatusBarHidden { return YES; }
Then the statusBar’s height is 0.
Alternatively, you can check out 'DeviceKit' pod. Once installed, all you need to do to check the device is:
import DeviceKit
let device = Device()
if device == .iPhoneX {
// place your code here
}
I think that Apple don't want us manually check if the device has "notch" or "home indicator" but the code that works is:
-(BOOL)hasTopNotch{
if (@available(iOS 11.0, *)) {
float max_safe_area_inset = MAX(MAX([[[UIApplication sharedApplication] delegate] window].safeAreaInsets.top, [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.right),MAX([[[UIApplication sharedApplication] delegate] window].safeAreaInsets.bottom, [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.left));
return max_safe_area_inset >= 44.0;
}
return NO;
}
-(BOOL)hasHomeIndicator{
if (@available(iOS 11.0, *)) {
int iNumberSafeInsetsEqualZero = 0;
if([[[UIApplication sharedApplication] delegate] window].safeAreaInsets.top == 0.0)iNumberSafeInsetsEqualZero++;
if([[[UIApplication sharedApplication] delegate] window].safeAreaInsets.right == 0.0)iNumberSafeInsetsEqualZero++;
if([[[UIApplication sharedApplication] delegate] window].safeAreaInsets.bottom == 0.0)iNumberSafeInsetsEqualZero++;
if([[[UIApplication sharedApplication] delegate] window].safeAreaInsets.left == 0.0)iNumberSafeInsetsEqualZero++;
return iNumberSafeInsetsEqualZero <= 2;
}
return NO;
}
Some of the other posts doesn't work. For example, iPhone 6S with "in-call status bar" (green bar) in portrait mode has a big top safe inset. With my code all the cases are taken up (even if device starts in portrait or landscape)
[[[UIApplication sharedApplication] delegate] window].safeAreaInsets
instead of just saving such value in a variable and use that?
Nov 2019:
Here's what I use in all of my production projects. Note that this gist is quite long.
This does not use computations of width or height, but rather: It checks the device string model. Does not have the risk of getting your build rejected by Apple because of using any private / undocumented APIs. Works with simulators 💯 import UIKit class DeviceUtility { /// Determines if the current device of the user is an iPhoneX type/variant. static var isIphoneXType: Bool { get { switch UIDevice().type { case .iPhoneXR, .iPhoneXS, .iPhoneXSMax, .iPhoneX, .iPhone11, .iPhone11Pro, .iPhone11ProMax: return true default: return false } } } } public enum DeviceModel : String { case simulator = "simulator/sandbox", // MARK: - iPods iPod1 = "iPod 1", iPod2 = "iPod 2", iPod3 = "iPod 3", iPod4 = "iPod 4", iPod5 = "iPod 5", // MARK: - iPads iPad2 = "iPad 2", iPad3 = "iPad 3", iPad4 = "iPad 4", iPadAir = "iPad Air ", iPadAir2 = "iPad Air 2", iPad5 = "iPad 5", //aka iPad 2017 iPad6 = "iPad 6", //aka iPad 2018 // MARK: - iPad Minis iPadMini = "iPad Mini", iPadMini2 = "iPad Mini 2", iPadMini3 = "iPad Mini 3", iPadMini4 = "iPad Mini 4", // MARK: - iPad Pros iPadPro9_7 = "iPad Pro 9.7\"", iPadPro10_5 = "iPad Pro 10.5\"", iPadPro12_9 = "iPad Pro 12.9\"", iPadPro2_12_9 = "iPad Pro 2 12.9\"", // MARK: - iPhones iPhone4 = "iPhone 4", iPhone4S = "iPhone 4S", iPhone5 = "iPhone 5", iPhone5S = "iPhone 5S", iPhone5C = "iPhone 5C", iPhone6 = "iPhone 6", iPhone6plus = "iPhone 6 Plus", iPhone6S = "iPhone 6S", iPhone6Splus = "iPhone 6S Plus", iPhoneSE = "iPhone SE", iPhone7 = "iPhone 7", iPhone7plus = "iPhone 7 Plus", iPhone8 = "iPhone 8", iPhone8plus = "iPhone 8 Plus", iPhoneX = "iPhone X", iPhoneXS = "iPhone XS", iPhoneXSMax = "iPhone XS Max", iPhoneXR = "iPhone XR", iPhone11 = "iPhone 11", iPhone11Pro = "iPhone 11 Pro", iPhone11ProMax = "iPhone 11 Pro Max", // MARK: - Apple TVs AppleTV = "Apple TV", AppleTV_4K = "Apple TV 4K", // MARK: - Unrecognized unrecognized = "?unrecognized?" } // #-#-#-#-#-#-#-#-#-#-#-#-#-#-# //MARK: UIDevice extensions // #-#-#-#-#-#-#-#-#-#-#-#-#-#-# public extension UIDevice { var type: DeviceModel { var systemInfo = utsname() uname(&systemInfo) let modelCode = withUnsafePointer(to: &systemInfo.machine) { $0.withMemoryRebound(to: CChar.self, capacity: 1) { ptr in String.init(validatingUTF8: ptr) } } let modelMap : [ String : DeviceModel ] = [ // MARK: - Simulators "i386" : .simulator, "x86_64" : .simulator, // MARK: - iPod "iPod1,1" : .iPod1, "iPod2,1" : .iPod2, "iPod3,1" : .iPod3, "iPod4,1" : .iPod4, "iPod5,1" : .iPod5, // MARK: - iPad "iPad2,1" : .iPad2, "iPad2,2" : .iPad2, "iPad2,3" : .iPad2, "iPad2,4" : .iPad2, "iPad3,1" : .iPad3, "iPad3,2" : .iPad3, "iPad3,3" : .iPad3, "iPad3,4" : .iPad4, "iPad3,5" : .iPad4, "iPad3,6" : .iPad4, "iPad4,1" : .iPadAir, "iPad4,2" : .iPadAir, "iPad4,3" : .iPadAir, "iPad5,3" : .iPadAir2, "iPad5,4" : .iPadAir2, "iPad6,11" : .iPad5, //aka iPad 2017 "iPad6,12" : .iPad5, "iPad7,5" : .iPad6, //aka iPad 2018 "iPad7,6" : .iPad6, // MARK: - iPad mini "iPad2,5" : .iPadMini, "iPad2,6" : .iPadMini, "iPad2,7" : .iPadMini, "iPad4,4" : .iPadMini2, "iPad4,5" : .iPadMini2, "iPad4,6" : .iPadMini2, "iPad4,7" : .iPadMini3, "iPad4,8" : .iPadMini3, "iPad4,9" : .iPadMini3, "iPad5,1" : .iPadMini4, "iPad5,2" : .iPadMini4, // MARK: - iPad pro "iPad6,3" : .iPadPro9_7, "iPad6,4" : .iPadPro9_7, "iPad7,3" : .iPadPro10_5, "iPad7,4" : .iPadPro10_5, "iPad6,7" : .iPadPro12_9, "iPad6,8" : .iPadPro12_9, "iPad7,1" : .iPadPro2_12_9, "iPad7,2" : .iPadPro2_12_9, // MARK: - iPhone "iPhone3,1" : .iPhone4, "iPhone3,2" : .iPhone4, "iPhone3,3" : .iPhone4, "iPhone4,1" : .iPhone4S, "iPhone5,1" : .iPhone5, "iPhone5,2" : .iPhone5, "iPhone5,3" : .iPhone5C, "iPhone5,4" : .iPhone5C, "iPhone6,1" : .iPhone5S, "iPhone6,2" : .iPhone5S, "iPhone7,1" : .iPhone6plus, "iPhone7,2" : .iPhone6, "iPhone8,1" : .iPhone6S, "iPhone8,2" : .iPhone6Splus, "iPhone8,4" : .iPhoneSE, "iPhone9,1" : .iPhone7, "iPhone9,3" : .iPhone7, "iPhone9,2" : .iPhone7plus, "iPhone9,4" : .iPhone7plus, "iPhone10,1" : .iPhone8, "iPhone10,4" : .iPhone8, "iPhone10,2" : .iPhone8plus, "iPhone10,5" : .iPhone8plus, "iPhone10,3" : .iPhoneX, "iPhone10,6" : .iPhoneX, "iPhone11,2" : .iPhoneXS, "iPhone11,4" : .iPhoneXSMax, "iPhone11,6" : .iPhoneXSMax, "iPhone11,8" : .iPhoneXR, "iPhone12,1" : .iPhone11, "iPhone12,3" : .iPhone11Pro, "iPhone12,5" : .iPhone11ProMax, // MARK: - AppleTV "AppleTV5,3" : .AppleTV, "AppleTV6,2" : .AppleTV_4K ] if let model = modelMap[String.init(validatingUTF8: modelCode!)!] { if model == .simulator { if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { if let simModel = modelMap[String.init(validatingUTF8: simModelCode)!] { return simModel } } } return model } return DeviceModel.unrecognized } }
Usage: let inset: CGFloat = DeviceUtility.isIphoneXType ? 50.0 : 40.0
I had to solve the same issue recently. And while this question is definitively answered ("No"), this may help others who need iPhone X specific layout behaviour.
I wasn't really interested in whether the device was iPhone X. I was interested in whether the device had a notched display.
private static var hasNotchedDisplay: Bool {
if let window = UIApplication.shared.keyWindow {
return (window.compatibleSafeAreaInsets.top > 20.0 || window.compatibleSafeAreaInsets.left > 0.0 || window.compatibleSafeAreaInsets.right > 0.0)
}
return false
}
You could also write a hasOnScreenHomeIndicator
variable along the same lines (though check the bottom safe area, maybe?).
The above uses my extension on UIView
for convenient access to the safe area insets on iOS 10 and earlier.
@objc public extension UIView {
@objc public var compatibleSafeAreaInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return safeAreaInsets
} else {
return .zero
}
}
@objc public var compatibleSafeAreaLayoutGuide: UILayoutGuide {
if #available(iOS 11.0, *) {
return safeAreaLayoutGuide
} else {
return layoutMarginsGuide
}
}
}
Success story sharing
userInterfaceIdiom
will return theUIUserInterfaceIdiomPhone
, too. This answer is wrong.