关于iOS:检测设备是否为iPhone X

Detect if the device is iPhone X

我的iOS应用程序为UINavigationBar使用自定义高度,这会导致新iPhoneX出现一些问题。

是否有人已经知道如何通过编程(在Objective-C中)可靠地检测iPhoneX上运行的应用程序?

编辑:

当然,检查屏幕的大小是可能的,但是,我想知道是否有一些"内置"的方法,如TARGET_OS_IPHONE检测iOS…

1
2
3
4
5
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
    CGSize screenSize = [[UIScreen mainScreen] bounds].size;
    if (screenSize.height == 812)
        NSLog(@"iPhone X");
}

编辑2:

我不认为我的问题是相关问题的副本。当然,有一些方法可以"测量"当前设备的不同属性,并使用结果来决定使用哪个设备。然而,这并不是我第一次编辑时试图强调的问题的实质所在。

实际问题是:"是否可以直接检测当前设备是iPhone X(例如通过某些SDK功能),还是必须使用间接测量?"

根据目前给出的答案,我假设答案是"不,没有直接的方法"。测量是前进的道路"。


根据你的问题,答案是否定的,没有直接的方法。有关详细信息,请在此处获取信息:

  • 如何在iOS上获得设备品牌和型号?

  • 如何用swift程序检查iPhone4和iPhone5的屏幕大小

iPhone x的高度为2436像素

从设备屏幕大小和分辨率:

enter image description here

从设备屏幕大小和方向:

enter image description here

Swift 3及更高版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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")

        case 2688:
            print("iPhone XS Max")

        case 1792:
            print("iPhone XR")

        default:
            print("Unknown")
        }
    }

ObjultC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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, 2208:
            printf("iPhone 6+/6S+/7+/8+");
            break;

        case 2436:
            printf("iPhone X, XS");
            break;

        case 2688:
            printf("iPhone XS Max");
            break;

        case 1792:
            printf("iPhone XR");
            break;

        default:
            printf("Unknown");
            break;
    }
}

Xamarin.iOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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");
    } else if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 2688) {
        Console.WriteLine("iPhone XS Max");
    } else if ((UIScreen.MainScreen.Bounds.Height * UIScreen.MainScreen.Scale) == 1792) {
        Console.WriteLine("iPhone XR");
    } else {
        Console.WriteLine("Unknown");
    }
}

根据您的问题如下:

或者使用screenSize.height作为float 812.0f而不是int 812

1
2
3
4
5
6
7
8
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");
    }

有关更多信息,请参阅iOS人机界面指南中的以下页面:

  • 适应性和布局.视觉设计.IOS.人机界面指南

Swift:

topNotch检测:

1
2
3
4
5
6
7
var hasTopNotch: Bool {
    if #available(iOS 11.0,  *) {
        return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 20
    }

    return false
}

ObjultC:

1
2
3
4
5
6
7
- (BOOL)hasTopNotch {
    if (@available(iOS 11.0, *)) {
        return [[[UIApplication sharedApplication] delegate] window].safeAreaInsets.top > 20.0;
    }

    return  NO;
}

更新:

不要使用userInterfaceIdiom属性来标识设备类型,因为userInterfaceIDiom的文档解释了:

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.

也就是说,此属性仅用于标识跑步应用程序的视图样式。然而,iphone应用程序(不是通用的)可以通过app store安装在ipad设备上,在这种情况下,userInterfaceIdiom也会返回UIUserInterfaceIdiomPhone

正确的方法是通过uname获得机器名。有关详细信息,请检查以下内容:

  • 如何在iOS上获得设备品牌和型号?


另一种可能是iOS11和iOS12,因为iPhoneX是唯一一款顶部有缺口、嵌入44的手机。这就是我在这里真正发现的:

ObjultC:

1
2
3
4
5
6
7
    BOOL iPhoneX = NO;
    if (@available(iOS 11.0, *)) {
        UIWindow *mainWindow = [[[UIApplication sharedApplication] delegate] window];
        if (mainWindow.safeAreaInsets.top > 24.0) {
            iPhoneX = YES;
        }
    }

斯威夫特4:

1
2
3
4
5
6
7
8
9
10
11
12
/// 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
}

当然,如果您是横向的,您可能需要检查左侧和右侧的安全区域插入。

编辑:_window是AppDelegate的ui窗口,在该窗口中,在Application DidFinishLaunchWithOptions中执行此检查。

回答已更新的iOS 12,以检查Top>24而不是Top>0。

编辑:在模拟器中,您可以转到硬件,在调用状态栏中切换。这样做表明,在iOS 11上的iPhone X或iPhone XS iOS 12打电话时,状态栏的高度不会改变。所有的变化都是时间图标,在这两种情况下,它都会得到一个绿色的背景。这是一个瞬间:

enter image description here


您应根据实际需要对iPhone X进行不同的检测。

用于处理顶部凹口(状态栏、导航栏)等。

1
2
3
4
5
6
7
8
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
}

用于处理底部主指示器(Tabbar)等。

1
2
3
4
5
6
7
8
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
}

用于背景大小、全屏功能等。

1
2
3
4
5
class var isIphoneXOrBigger: Bool {
    // 812.0 on iPhone X, XS.
    // 896.0 on iPhone XS Max, XR.
    return UIScreen.main.bounds.height >= 812
}

注:最终与UIDevice.current.userInterfaceIdiom == .phone混合注意:此方法需要有一个启动屏幕故事板或适当的启动

用于背景比例、滚动功能等。

1
2
3
4
5
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
}

注意:此方法需要有一个启动屏幕故事板或适当的启动

用于分析、统计、跟踪等。

获取机器标识符并将其与记录的值进行比较:

1
2
3
4
5
6
7
8
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"
}

要将模拟器作为有效的iPhone X包含在分析中,请执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
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"
}

要包括iphone xs、xs max和xr,只需查找以"iphone11"开头的型号:

1
return model =="iPhone10,3" || model =="iPhone10,6" || model.starts(with:"iPhone11,")

用于FaceID支持

1
2
3
4
5
6
7
8
import LocalAuthentication
/// will fail if user denies canEvaluatePolicy(_:error:)
class var canUseFaceID: Bool {
    if #available(iOS 11.0, *) {
        return LAContext().biometryType == .typeFaceID
    }
    return false
}


您可以这样做来根据尺寸检测iPhoneX设备。

迅捷

1
2
3
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
   //iPhone X
}

目标-C

1
2
3
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && UIScreen.mainScreen.nativeBounds.size.height == 2436)  {
  //iPhone X    
}

enter image description here

但是,

这是不够的。如果苹果宣布下一款iPhone的尺寸与iPhoneX相同,那么最好的方法就是使用硬件字符串来检测设备。

对于较新的设备,硬件字符串如下。

iPhone 8-iPhone10,1或iPhone 10,4

iPhone 8 Plus-iPhone10、2或iPhone 10、5

iPhone X-iPhone10、3或iPhone10、6


查看设备型号/机器名称,不要直接在代码中使用点/像素计数,这是硬代码,对设备硬件没有意义。

1
2
3
4
5
6
7
8
9
10
#import <sys/utsname.h>

NSString* deviceName()
{
    struct utsname systemInfo;
    uname(&systemInfo);

    return [NSString stringWithCString:systemInfo.machine
                          encoding:NSUTF8StringEncoding];
}

结果:

1
2
@"iPhone10,3" on iPhone X (CDMA)
@"iPhone10,6" on iPhone X (GSM)

请参阅此答案。

完整代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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)
{
    NSString *strModelID = GetDeviceModel();

    return [strModelID isEqualToString:@"iPhone10,3"] || [strModelID isEqualToString:@"iPhone10,6"] || // iPhone X
           [strModelID isEqualToString:@"iPhone11,2"] || [strModelID isEqualToString:@"iPhone11,4"] || [strModelID isEqualToString:@"iPhone11,6"] || // iPhone XS (Max)
           [strModelID isEqualToString:@"iPhone11,8"]; // iPhone XR
}


1
2
3
4
5
6
7
#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是u iphone_x(是u iphone&;&;[[uiscreen主屏幕]边界)。size.height==812.0)

1
2
3
4
5
#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"]

注意:小心,它只适用于纵向


在看了所有答案之后,这就是我最终要做的:

解决方案(swift 4.1兼容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
    }
}

使用

1
2
3
4
5
if UIDevice.isIphoneX {
    // is iPhoneX
} else {
    // is not iPhoneX
}

注释

pre-swift 4.1您可以检查应用程序是否在类似这样的模拟器上运行:

1
TARGET_OS_SIMULATOR != 0

从Swift 4.1开始,您可以使用目标环境平台条件检查应用程序是否在模拟器上运行:

1
2
3
4
5
#if targetEnvironment(simulator)
    return true
#else
    return false
#endif

(旧方法仍然有效,但这种新方法更具前瞻性)


所有这些基于维度的答案都容易受到未来设备上错误行为的影响。他们今天就可以工作了,但是如果明年有一部同样大小的iPhone,但是在玻璃下有摄像头等,所以没有"凹口"呢?如果唯一的选择是更新应用程序,那么对于您和您的客户来说,这是一个糟糕的解决方案。

您也可以检查硬件型号字符串,如"iphone10,1",但这有问题,因为有时苹果会为世界各地的不同运营商发布不同的型号。

正确的方法是重新设计顶部布局,或者解决自定义导航栏高度的问题(这就是我要关注的)。但是,如果你决定不做这两件事中的任何一件,请意识到无论你在做什么,今天都是一个让它工作的黑客,你需要在某个时候纠正它,也许是多次,以保持黑客的工作。


Swift 4+答案

iPhone X、xr、xs、xsmax:

注:测试需要实际设备

参考文献

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 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")
        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
    }
}


是的,这是可能的。下载uidevice硬件扩展(或通过cocoapod"uidevice硬件"安装),然后使用:

1
2
NSString* modelID = [[[UIDevice currentDevice] modelIdentifier];
BOOL isIphoneX = [modelID isEqualToString:@"iPhone10,3"] || [modelID isEqualToString:@"iPhone10,6"];

请注意,这在模拟器中不起作用,只在实际设备上起作用。


我知道这只是一个迅速的解决办法,但它可以帮助别人。

我在每个项目中都有globals.swift,我经常添加的一个东西是DeviceType,以便轻松检测用户的设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct ScreenSize {
  static let width = UIScreen.main.bounds.size.width
  static let height = UIScreen.main.bounds.size.height
  static let frame = CGRect(x: 0, y: 0, width: ScreenSize.width, height: ScreenSize.height)
  static let maxWH = max(ScreenSize.width, ScreenSize.height)
}

struct DeviceType {
  static let iPhone4orLess = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxWH < 568.0
  static let iPhone5orSE   = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxWH == 568.0
  static let iPhone678     = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxWH == 667.0
  static let iPhone678p    = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxWH == 736.0
  static let iPhoneX       = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxWH == 812.0
  static let iPhoneXRMax   = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 896.0
  static var hasNotch: Bool {
    return iPhoneX || iPhoneXRMax
  }
}

然后使用它:

1
2
3
4
5
6
7
if DeviceType.hasNotch {
  print("This executes on all phones with a notch")
}

if DeviceType.iPhone678 {
  print("This executes on iPhones 6, 7 and 8")
}

如果在项目中使用LaunchImage,请确保为所有支持的设备(如xs max、xr)添加图像,因为没有这些设备UIScreen.main.bounds不会返回正确的值。


根据@saswanb的回复,这是一个swift 4版本:

1
2
3
4
5
6
var iphoneX = false
if #available(iOS 11.0, *) {
    if ((UIApplication.shared.keyWindow?.safeAreaInsets.top)! > CGFloat(0.0)) {
        iphoneX = true
    }
}


Swift 4可重复使用扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    public extension UIDevice {

    public enum `Type` {
        case iPad
        case iPhone_unknown
        case iPhone_5_5S_5C
        case iPhone_6_6S_7_8
        case iPhone_6_6S_7_8_PLUS
        case iPhone_X_Xs
        case iPhone_Xs_Max
        case iPhone_Xr
    }

    public var hasHomeButton: Bool {
        switch type {
        case .iPhone_X_Xs, .iPhone_Xr, .iPhone_Xs_Max:
            return false
        default:
            return true
        }
    }

    public var type: Type {
        if userInterfaceIdiom == .phone {
            switch UIScreen.main.nativeBounds.height {
            case 1136:
                return .iPhone_5_5S_5C
            case 1334:
                return .iPhone_6_6S_7_8
            case 1920, 2208:
                return .iPhone_6_6S_7_8_PLUS
            case 2436:
                return .iPhone_X_Xs
            case 2688:
                return .iPhone_Xs_Max
            case 1792:
                return .iPhone_Xr
            default:
                return .iPhone_unknown
            }
        }
        return .iPad
   }
}


所有使用height的答案都只是故事的一半,原因之一是。如果在设备方向为landscapeLeftlandscapeRight时要这样检查,检查将失败,因为heightwidth交换。

这就是为什么我的解决方案在Swift4.0中看起来像这样:

1
2
3
4
5
6
7
8
9
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
    }
}


您不应该假设苹果发布的唯一具有不同uinavigationbar高度的设备是iPhoneX。请尝试使用更通用的解决方案来解决这个问题。如果您希望条总是比其默认高度大20px,则代码应该将20px添加到条的高度,而不是将其设置为64px(44px+20px)。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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
    }
}

例子:

1
2
3
if UIApplication.isDeviceWithSafeArea {
     //e.g. change the frame size height of your UITabBar
}

1
2
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0f)


对于那些获得2001px而不是2436px的本地边界高度(像我一样),这是因为您在iOS 11(xcode 8而不是xcode 9)之前使用旧的SDK构建了应用程序。使用旧版的SDK,iOS将在iPhoneX上显示"黑盒"应用程序,而不是将屏幕边缘扩展到边缘,超出顶部的"传感器缺口"。这会减小屏幕大小,这就是该属性返回2001而不是2436的原因。

最简单的解决方案是,如果您只对设备检测感兴趣,只需检查两种尺寸。我使用这种方法在使用旧的Xcode SDK构建时检测faceid,该SDK没有指定生物特征类型的枚举值。在这种情况下,使用屏幕高度进行设备检测似乎是了解设备是否具有faceid和touchid的最佳方法,而无需更新xcode。


通常,程序员需要它来约束到顶部或底部,因此这些方法可以帮助

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
}

在iPhone X之前,这些方法返回:0

对于iPhone X:44和34

然后将这些附加项添加到顶部或底部约束


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (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;
    }
}


不要像其他解决方案所建议的那样使用屏幕像素大小,这很糟糕,因为这可能会导致未来设备出现误报;如果uiwindow尚未呈现(appdelegate),将无法在景观应用程序中工作,并且如果设置了比例,可能会在模拟器上失败。

相反,我已经为此目的制作了一个宏,它非常容易使用,并且依赖硬件标志来防止上述问题。

编辑:更新以支持iphonex、iphone xs、iphonexr、iphone xs max

使用:

1
2
3
if (IS_DEVICE_IPHONEX) {
    //do stuff
}

是的,真的。

宏:

只要把这个复制粘贴到任何地方,我宁愿把.h文件的最底部放在@end之后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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]);\
})()\
)


我当时使用的是彼得·克雷因茨的代码(因为它很干净,而且做了我需要的事情),但后来我意识到,当设备在肖像上时,它就工作了(显然,顶部填充将在顶部)因此,我创建了一个扩展来处理所有方向及其各自的填充,而不在屏幕大小上中继:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
    }
}

在你的呼叫网站上,你只是:

1
let res = UIDevice.current.isIphoneX

我依靠状态栏的框架高度来检测它是否是iPhoneX:

1
2
3
if UIApplication.shared.statusBarFrame.height >= CGFloat(44) {
    // It is an iPhone X
}

这是用于非纵向应用。您还可以根据设备方向检查大小。另外,在其他iPhone上,状态栏可能隐藏,因此框架高度为0。在iPhoneX上,状态栏永远不会被隐藏。


我详细阐述了你对别人的回答,并迅速扩展了我的观点。我喜欢斯威夫特Enums和"一切井然有序"的原子化。我已经创建了在设备和模拟器上都有效的解决方案。

优势:-简单的接口,例如UIDevice.current.isIPhoneX。-UIDeviceModelType枚举使您能够轻松扩展要在应用程序中使用的特定于模型的功能和常量,例如cornerradius

缺点:-这是特定于型号的解决方案,而不是特定于分辨率的解决方案——例如,如果苹果生产的另一个型号规格相同,这将无法正常工作,您需要添加另一个型号才能使其正常工作=>您需要更新您的应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
    }
}


或者,您可以查看"devicekit"pod。安装后,检查设备只需:

1
2
3
4
5
import DeviceKit
let device = Device()
if device == .iPhoneX {
  // place your code here
}

我最近不得不解决同样的问题。虽然这个问题得到了明确的回答("否"),但这可能有助于其他需要iPhoneX特定布局行为的用户。

我对这个设备是否是iPhoneX并不感兴趣,我对这个设备是否有凹口显示感兴趣。

1
2
3
4
5
6
7
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
}

您还可以沿着相同的行编写一个hasOnScreenHomeIndicator变量(尽管可能检查底部安全区域?).

上面使用了我在UIView上的扩展,方便访问iOS 10及更早版本上的安全区域插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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
        }
    }
}

为了快速解决问题,我喜欢这样:

1
let var:CGFloat = (UIDevice.current.userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436) ? <iPhoneX> : <AllOthers>

我认为苹果不希望我们手动检查设备是否有"缺口"或"主指示器",但工作的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-(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;

}

其他一些帖子不起作用。例如,iPhone6s的"通话状态栏"(绿色栏)在纵向模式下有一个大的顶部安全插入。使用我的代码,所有的情况都会被占用(即使设备以纵向或横向方式启动)


有几个理由想知道这个设备是什么。

  • 您可以检查设备高度(和宽度)。这对布局很有用,但如果您想知道确切的设备,通常不想这样做。

  • 出于布局目的,您还可以使用UIView.safeAreaInsets

  • 例如,如果要显示要包含在电子邮件中用于诊断的设备名称,则在使用sysctl ()检索设备模型后,可以使用此等效项来计算名称:

    1
    2
    3
    $ curl http://appledevicenames.com/devices/iPhone10,6

    iPhone X

  • 检测设备是否为iPhone X的最佳和最简单方法是,

    https://github.com/stephanheilner/uidevice-displayname

    1
    2
    3
    4
    5
    6
    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)))}

    对于iPhoneX,标识符为"iphone10,3"或"iphone10,6"。


    仅在Portrait中,我使用视图框架的宽度和高度来检查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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")
        }

    }

    这里列出了纵向屏幕尺寸

    enter image description here


    随着iOS12的发布,像iPhoneX这样的设备可能属于这一类别。

    `

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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
        }

    }`

    使用简单的方法检测任何设备。像下面一样,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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
    }

    像这样打电话,

    1
    2
    3
    if isPhoneDevice() {
         // Your code
    }