Detect if the device is iPhone X
Asked Answered
M

38

285

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".

Mobile answered 13/9, 2017 at 8:12 Comment(11)
iPhone X has a screen resolution different from others.Dudek
Yes, as I mentioned in my edit it possible to check the screen size. However the question is, if there is "direct" method to query the device type rather than "indirect" measurements...Mobile
it will return you 812 if you upload Default image for iPhone X. Till then I think it will return you iPhone 7 size.. not sure...Ory
Possible duplicate of How to get device make and model on iOS?Topography
The author just wants to get the device type, not the screen resolution. Why not check the machine name directly? @Topography is right.Kall
#27776279Cost
there is no direct method to do it, because if that is the answer to your question then you are raising the wrong question about the situation.Linkman
#26029418Northington
why don't you just use the safe-area guides as it is recommended by Apple?Linkman
IMPORTANT, future devs: Do not detect this utilizing screen height as current top solutions suggest, it is bad as it can result in false positives for future devices; will not work if UIWindow hasn't yet rendered (like in your AppDelegate init functions), won't work in landscape apps, and can fail on simulator if scale is set. NEVER use magic numbers for things like this! You can check hardware flags to guarantee success like I have done here: https://mcmap.net/q/107572/-detect-if-the-device-is-iphone-xLoquacity
I am a React/RN Developer, so I won't even begin to pretend I know Swift or Xcode.. That being said, I've fixed this issue by creating a settings switch in my apps that allows the user to save their preferred top margin of their app window. Of course it could say anything you want on the switch. (Notch Adjust, Top Space, etc) Does IOS/Xcode not allow something like this? Then, no matter future phones or builds, your app won't need to be updated if models change.Unemployed
C
423

Based on your question, the answer is no. There are no direct methods. For more information you can get the information here:

and

The iPhone X height is 2436 px

From Device Screen Sizes and resolutions:

enter image description here

From Device Screen Sizes and Orientations:

enter image description here

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:

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:

Chiliarch answered 13/9, 2017 at 8:41 Comment(34)
The resolution of iPhone X is 2436 x 1125 pixels according to : iphonesoft.fr/2017/09/12/…Dispart
@Dispart - the resolution of iphone X is - 1125 x 2436 pixels (~458 ppi pixel density)Chiliarch
NO! The iPhone app (not the universe) could be installed in iPad device via App store, in that case, the userInterfaceIdiom will return the UIUserInterfaceIdiomPhone, too. This answer is wrong.Kall
@Kall - I agree your point, is this possible to update the answer based on your comment, its useful for many usersChiliarch
The size returned for iPhone X seems to be, oddly, (width = 1125, height = 2001). Any idea why this is?Deeann
@Adam- are you called in viewdidload or the elseChiliarch
This answer is partially wrong. It won't detect iPhone 6 Plus, 6S Plus, 7 Plus or 8 Plus when using a real device. Check the comments on my answer updated to fix that issue https://mcmap.net/q/109949/-how-to-check-screen-size-of-iphone-4-and-iphone-5-programmatically-in-swiftCost
And btw it looks like a verbatim copy paste of my old answer without proper author attribution. It was copied before it has been edited because it still have the first case I removed that returns "iPhone Classic" which will never be executed stackoverflow.com/revisions/…Cost
stackoverflow.com/revisions/…Cost
@LeoDabus - you are said answer related to this #25780783Chiliarch
@LeoDabus - if I used your answer I surely mention here like as I was taken the answer from here for ref purposeChiliarch
I am pretty sure your switch comes from my answer. if UIDevice().userInterfaceIdiom == .Phone { switch UIScreen.mainScreen().nativeBounds.height { case 480: print("iPhone Classic") case 960: print("iPhone 4 or 4S") case 1136: print("iPhone 5 or 5S or 5C") case 1334: print("iPhone 6 or 6S") case 2208: print("iPhone 6+ or 6S+") default: print("unknown") } } stackoverflow.com/revisions/…Cost
It was edited you can see the revision link above. It also has the same issue as my original answer as I commnented it would work for simulator but not for the actual device due to downsampling on the iPhones plus. You should check for two different heights one for simulator and another for the real devicesCost
Let us continue this discussion in chat.Chiliarch
@Chiliarch Don't add Swift tag for objective c questions only because you added the Swift version to it. Besides that your iPhone 6, 6s, 7, 8 (PLUS) it is wrong as I already mentioned in my comment above. 2208 will only work for simulator. The real device height returned by nativeBounds.height is 1920 because of the downsampling. If you need to support simulator and device for iPhone whatever Plus you need check against both cases case 1920, 2208:Cost
i think we should use screenSize.height == 812.0f || screenSize.width == 812.0f, since the device can be in landscape.Iq
But on iPhoneX simulator, it's height is 1704.Prodigious
@ThreeCoins, please update your answer for plus devices as per suggestion of Leo Dabus. It works on Plus simulator but not on device.Sandstone
If you run from landscape, how to determine by height ?Meaganmeager
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. You can check hardware flags to guarantee success like I have done here: https://mcmap.net/q/107572/-detect-if-the-device-is-iphone-xLoquacity
@Anbu.karthik: Thanks for the answer. Just found that you were missing 'f' after 'print' for the line "print("iPhone Xs Max")".Abuttals
@ShobhitC - thanks for your point , I added the f in objective CChiliarch
*hasTopNotch implementation does not work correctly when personal hotspotting!Pyelonephritis
@agandi- okay , I will check and let u knowChiliarch
@itachi it's worth noting that even tho the app is installed on an iPad, it will be displayed as an iPhone app which sort of fits the provided idiom.Bronwen
reason for downvote ,if you tell the reason I will update the answer if its validChiliarch
Don't direct initialize, use UIDevice.current.userInterfaceIdiom instead.Tilda
The screen height for the iPhone 11 is actually 1624 and requires a separate case statement.Meathead
@Meathead - thanks for your update bro, I will check and update hereChiliarch
@Meathead - check this - iPhone 11 & XR shows the same size. please update if I made any mistakeChiliarch
@Chiliarch when I logged out the iPhone 11 screen height on my simulator via this method: [[UIScreen mainScreen] nativeBounds].size.height it returned 1624.Meathead
@Meathead - oh okay, please update the answer what you getChiliarch
If you use SceneDelegate on iOS 13, you need to change UIApplication.shared.delegate?.window??.safeAreaInsets.top to UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.safeAreaInsets.topProfessoriate
@Professoriate - thanks for your valuable info, I updated the answer please check.Chiliarch
O
108

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:

enter image description here

Occipital answered 14/9, 2017 at 19:50 Comment(11)
The safe area insets will contain the status bar height, if one is visible, on other devices. Checking if this is 0 will only tell you if the status bar is visible, not if the device is an iPhone X.Muslim
I am not finding that to be true, if you do the check on the AppDelegate window. I should have provided that context for the _window variable above. Thank you for the clarification.Occipital
"This may break in iPhone Xs or iPhone 11", said Cook.Kall
I've adapted a little and use if _window.safeAreaInsets != UIEdgeInsets.zero to allow for any device orientationAlgerian
If you don't want to use .top, safeAreaInsets.bottom will be 34 on iPhone X and 0 on other devicesLindbom
Warning: Don't use this, it breaks on iOS 12. It's also not documented what UIWindow should do in this case. openradar.appspot.com/42372793Persis
I made it a bit more future-proof by checking the bottom or left (for landscape) instead if (mainWindow.safeAreaInsets.bottom > 0.0 || mainWindow.safeAreaInsets.left > 0.0)Chirr
How does this work if the status bar has a increased height while on a call?Anthropogeography
Good question @Maximilian, I checked in a simulator because I don't have one of these luscious devices myself. Answer updated.Occipital
This is not a good solution anymore! Condition (safeAreaInsets.top > 20.0) is true on iPad Pro from 2018 when view controller with visible Status Bar is presented!Shipboard
@Shipboard Then change 20 to 24 and you are good, thanks for the update. Answer updated.Occipital
I
80

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
}
Inflorescence answered 2/11, 2017 at 3:49 Comment(7)
I was hoping that the return LAContext().biometryType == .typeFaceID would work even if the user had denied canEvaluatePolicy, but it doesn't work for me, it still returns .noneOffense
Well @Jeremy, it's a documented behavior, consequence of Apple privacy policy. That's why the comment above the method.Terminator
Ah, I misinterpreted your comment. I thought you meant using canEvaluatePolicy could fail, so use the following instead. I find it a bit odd that you are allowed it check if the device has Face ID until the user responds to the toggle and then you can't even check anymore. How am I supposed to provide a helpful error message to say to go to Settings and toggle Face ID?Offense
@Offense I don't own an iPhone X, so I don't know. Maybe you could use the model detection above (model == "iPhone10,3" || model == "iPhone10,6"), and if canUseFaceID returns false, then it means it was denied by user.Terminator
Is restricted by Apple call to LAContext or sysctlbyname? I mean, Can Apple reject the app if we use those methods?.Cohabit
@MateoOlaya Nothing in my answer would be rejected by Apple: you can use it all.Terminator
this doesn't work if there is a personal hotspot connectionPyelonephritis
D
43

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     
}

enter image description here

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

Dode answered 18/9, 2017 at 12:2 Comment(2)
You should to use [UIDevice currentDevice] instead of [[UIDevice alloc] init]Ailurophile
the only problem with the hardware string is it doesn't work on the simulatorTurret
K
43

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()];
}
Kall answered 20/9, 2017 at 7:51 Comment(3)
Excellent answer since it handles the simulator correctly. Please add the #import line to the "full code" section. I missed that (copy/pasted) on my first attempt.Mcquillin
that is my preferred method. Refer to this wiki for a complete list of device model strings. As a side comment, @"iphone10,3" could also be viewed as hard code.Fatuity
@Fatuity Yes, it's really a critical controversial issue. The hardware model string has a unique identifier than screen points for the device, I think. Generally, it's used for data statistics.Kall
R
26
#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

Realistic answered 21/9, 2017 at 10:25 Comment(3)
Be careful, it works fine only for portrait orientationDemulcent
Thanks for this. Works well. In Landscape mode you need to adjust those numbers. The magic number of iPhoneX in Landscape mode is 375.0Frumenty
There are a few iPhone Plus/Max/Pro using nativeScale with 3.0, right?Kall
S
26

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)

Steiger answered 30/11, 2017 at 5:2 Comment(2)
is apple will be fine with this ?Lissotrichous
@commando24 Yes, I don't see any reason for them to reject the app because of this code.Steiger
I
18

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.

Internationale answered 19/9, 2017 at 2:29 Comment(2)
Right. Refining an assumption that number X will always be A to one that number X will always be A unless condition Y when it'll be B is just digging deeper. Size based on the Apple-nominated safe area, not by second-guessing it.Rosemaria
I'll worry about the next iPhone when it's actually out there. I want my app to work TODAY.Swampland
I
16

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
    }
   }
}
Intelligencer answered 15/10, 2018 at 16:47 Comment(2)
good extension, but most usefull here is UIDevice.current.hasHomeButtonLogogriph
@Intelligencer is it good to use userInterfaceIdiom for determining devices for universal app ? most of the people does not recommend this. is there any harm to use it ?Legator
S
15

SWIFT 4+ Answer

iPhone X, XR, XS, XSMAX, 11 Pro, 11 Pro Max:

Note: Need real device for test

Reference

 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
    }
}
Sinistrad answered 10/10, 2017 at 15:9 Comment(2)
For method 1, you can take away the "var window" property outside the func and just a "let" constant within it (type UIWindow, i.e. not optional). I like this answer since at startup, self.view.window may be nil, and UIApplication.shared.keyWindow may likewise be nil, whereas creating a UIWindow in this way works every time.Humphreys
this should be the actual answer. Everyone here is using device screen hight as the determining factor. Apple could easily release another device of the same size (which they did the following year with the iPhone XS). This takes the actual device model from UNIX.Sensuality
S
10

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.

Submicroscopic answered 14/9, 2017 at 4:27 Comment(3)
All device code here : iphonesoft.fr/2016/10/31/… Example : iPhone X : iPhone10,5 and iPhone10,6Dispart
The Hardware strings from wikipedia said "iPhone10,3 and iPhone10,6". @DispartKall
@Medhi, you can use ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIF‌​IER"] in Simulator to get the actual values from Xcode.Terminator
M
10

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")
}
Melodramatic answered 30/10, 2017 at 14:21 Comment(1)
Friend new to Swift asked how to use this, just in case someone else doesn’t know… if DeviceType.iPhoneX { //do something for iPhone X notch }else{ // don’t do anything about notch }Cockeyed
P
9

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
    }
}
Phonsa answered 19/9, 2017 at 14:44 Comment(2)
The status bar is also considered outside the safe area! so this will return false positives! It should be higher then 20points (height of statusbar). This returns also true if the device is iPhone Xs, R or Xs Max.Naphtha
code works great, but be careful: keyWindow is nil until the main view controller has called viewDidAppearKrefetz
G
5

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
    }
}
Galwegian answered 27/10, 2017 at 21:21 Comment(1)
Just use nativeBounds insteadCost
L
5

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]);\
})()\
)
Loquacity answered 25/7, 2018 at 6:12 Comment(1)
The only reason I can think for detecting iPhoneX is to avoid the notch at top of screen; if so you can check the safeArea.top to detect the size of said notch. Just make sure you measure it after UIWindow has loaded, so not during viewDidLoad but one thread-cycle after: if (@available(iOS 11.0, *)) { [UIApplication sharedApplication].keyWindow.safeAreaInsets.top }Loquacity
G
5

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

enter image description here

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")
    }
}
Gorged answered 16/9, 2018 at 0:55 Comment(0)
M
4

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

Muslim answered 18/9, 2017 at 23:57 Comment(2)
So, what other solution do you have to propose ?Aideaidedecamp
@xaphod there are better answers now.Terminator
C
4
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
}
Cantus answered 10/11, 2017 at 6:4 Comment(0)
L
4

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
}
Lector answered 17/1, 2018 at 10:3 Comment(0)
M
3
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0f)
Micronutrient answered 13/9, 2017 at 8:39 Comment(1)
it will return you 812 if you upload Default image for iPhone X. Till then I think it will return you iPhone 7 size, not sure though...Ory
B
3
- (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;
    }
}
Bowsprit answered 19/10, 2017 at 11:52 Comment(1)
Best answer! Without need of any device size pixel value.Lector
M
3

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

Meltage answered 17/11, 2017 at 12:0 Comment(0)
G
3

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.

Gabbert answered 14/12, 2017 at 18:31 Comment(0)
M
3

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
Mezzotint answered 20/2, 2018 at 8:32 Comment(0)
M
2

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
    }
}
Mainstay answered 8/12, 2017 at 13:45 Comment(1)
Instead of using Mirror, it will be faster to use sysctlbyname as done in Cloud9999Strife answer (and in my answer too).Terminator
N
2

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.

Nippur answered 20/12, 2017 at 17:39 Comment(2)
You can hide iPhoneX statusBar in controller with this: - (BOOL)prefersStatusBarHidden { return YES; } Then the statusBar’s height is 0.Platino
@无夜之星辰 I check this at boot time in the AppDelegate.Nippur
C
2

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
}
Courteous answered 4/8, 2018 at 16:57 Comment(0)
U
2

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)

Ultramodern answered 16/11, 2018 at 19:14 Comment(4)
Why do you keep making calls to [[[UIApplication sharedApplication] delegate] window].safeAreaInsetsinstead of just saving such value in a variable and use that?Lister
Because it's mutable over the time and you have to catch it on the flyUltramodern
Catch it on the fly?! For what? I fail to understand how is that even going to catch anything on the fly since both calls are probably being made on the main thread anyway. From what I can see, it will only waste processing, and make the code ugly. Am I wrong?Lister
First of all, Apple definitively doesn't want us to calculate this manually (use adaptative layouts instead). Safe area may change if for example device is iPhone 5S and appears in call green bar (green bar indicator you are attending a phone call). Finally, in the first method that is called when your app starts you can catch the values as boolean and read them instead calling hasTopNotch() and hasHomeIndicator() every time. Example: bool bMyAppHasTopNotch = [self hasTopNotch]; and then read bMyAppHasTopNotch instead calling [self hasTopNotch] every time. Hope I have helped you.Ultramodern
G
2

Nov 2019:

Here's what I use in all of my production projects. Note that this gist is quite long.

  1. This does not use computations of width or height, but rather:
  2. It checks the device string model.
  3. Does not have the risk of getting your build rejected by Apple because of using any private / undocumented APIs.
  4. 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

Gamo answered 18/11, 2019 at 7:29 Comment(1)
Works perfectly. Thanks. I'm using it in a SwiftUI project.Horowitz
I
1

There are several reasons to want to know what the device is.

  1. You can check the device height (and width). This is useful for layout, but you usually don't want to do that if you want to know the exact device.

  2. For layout purposes, you can also use UIView.safeAreaInsets.

  3. If you want to display the device name, for example, to be included in a email for diagnostic purposes, after retrieving the device model using sysctl (), you can use the equivalent of this to figure the name:

    $ curl http://appledevicenames.com/devices/iPhone10,6
    
    iPhone X
    
Irremeable answered 14/10, 2017 at 15:48 Comment(0)
V
1

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
        }
    }
}
Verdaverdant answered 1/6, 2018 at 3:32 Comment(0)
O
1

The best and easiest way to detect if the device is iPhone X is,

https://github.com/stephanheilner/UIDevice-DisplayName

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

And identifier is either "iPhone10,3" or "iPhone10,6" for iPhone X.

Openwork answered 29/6, 2018 at 9:22 Comment(0)
G
1

I am trying to make work the previous replies and none of them worked for me. So I found one solution for SwiftUI. Creating a file called UIDevice+Notch.swift

And its content:

extension UIDevice {
    var hasNotch: Bool {
        let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
        return bottom > 0
    }
}

Usage:

if UIDevice.current.hasNotch {
    //... consider notch 
} else {
    //... don't have to consider notch 
}
Guanase answered 12/12, 2019 at 18:42 Comment(1)
UIApplication keyWindow has been deprecated since iOS 13.0.Zone
K
1

How to detect iOS device models and screen size?

CheckDevice is detected the current  device model and screen sizes.

You can also use

CheckDevice.size() returned for iPhone 12 mini's .screen5_4Inch

etc... maybe...

CheckDevice.isPhone()

to check the device type iPhone.

CheckDevice.isWatch()

CheckDevice.isSimulator()

CheckDevice.isPad()

Great repo

CheckDevice https://github.com/ugurethemaydin/CheckDevice

Kiruna answered 18/11, 2020 at 20:6 Comment(0)
M
-1

For a quick fix, I like this:

let var:CGFloat = (UIDevice.current.userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436) ? <iPhoneX> : <AllOthers>
Microdot answered 4/1, 2018 at 23:9 Comment(0)
C
-1

With the release of iOS 12, devices like iPhone X may come under this category.

`

extension UIDevice {
    var isPortrait: Bool {

       return UIDeviceOrientationIsPortrait(orientation) ||
      UIInterfaceOrientationIsPortrait(UIApplication.shared.statusBarOrientation)

  }

   var isDeviceWith_XShape : Bool {

    if self.userInterfaceIdiom == .phone {

        if isPortrait
        {
            switch UIScreen.main.nativeBounds.height {

            case 2436,2688,1792:
                print("iPhone X, Xs, Xr, Xs Max")
                return true
            default:
                print("Any other device")
                return false
            }
        }
        else
        {
            switch UIScreen.main.nativeBounds.width {

            case 2436,2688,1792:
                print("iPhone X, Xs, Xr, Xs Max")
                return true
            default:
                print("Any other device")
                return false
            }
        }


    }
    else
    {
        return false
    }

}`
Callahan answered 24/9, 2018 at 11:14 Comment(0)
R
-1

To detect any of the devices by using simple methods. like below,

func isPhoneDevice() -> Bool {
    return UIDevice.current.userInterfaceIdiom == .phone
}

func isDeviceIPad() -> Bool {
    return UIDevice.current.userInterfaceIdiom == .pad
}

func isPadProDevice() -> Bool {
    let SCREEN_WIDTH: CGFloat = UIScreen.main.bounds.size.width
    let SCREEN_HEIGHT: CGFloat = UIScreen.main.bounds.size.height
    let SCREEN_MAX_LENGTH: CGFloat = fmax(SCREEN_WIDTH, SCREEN_HEIGHT)

    return UIDevice.current.userInterfaceIdiom == .pad && SCREEN_MAX_LENGTH == 1366.0
}

func isPhoneXandXSDevice() -> Bool {
    let SCREEN_WIDTH = CGFloat(UIScreen.main.bounds.size.width)
    let SCREEN_HEIGHT = CGFloat(UIScreen.main.bounds.size.height)
    let SCREEN_MAX_LENGTH: CGFloat = fmax(SCREEN_WIDTH, SCREEN_HEIGHT)

    return UIDevice.current.userInterfaceIdiom == .phone && SCREEN_MAX_LENGTH == 812.0
}

func isPhoneXSMaxandXRDevice() -> Bool {
    let SCREEN_WIDTH = CGFloat(UIScreen.main.bounds.size.width)
    let SCREEN_HEIGHT = CGFloat(UIScreen.main.bounds.size.height)
    let SCREEN_MAX_LENGTH: CGFloat = fmax(SCREEN_WIDTH, SCREEN_HEIGHT)

    return UIDevice.current.userInterfaceIdiom == .phone && SCREEN_MAX_LENGTH == 896.0
}

and call like this,

if isPhoneDevice() {
     // Your code
}
Redintegrate answered 17/10, 2018 at 5:3 Comment(1)
the new iPad Pro 11.0 can easy be detected, because 1194x834 is unique, but the new iPad Pro 12.9 3rd gen has same sizes as iPadPro before. HowTo differ between them ?Floorboard
R
-1

Here are two macros for those needing this in Objective-C.

#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_X (IS_IPHONE && [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.top > 24.0)

Usage:

if (IS_IPHONE_X) {
}

I hope it can help others.

Ratter answered 8/1, 2020 at 19:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.