我的iOS应用程序为UINavigationBar使用了自定义高度,这在新的iPhone X上导致了一些问题。

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

编辑:

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

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

编辑2:

我不认为,我的问题是一个重复的关联问题。当然,有一些方法可以“测量”当前设备的不同属性,并使用结果来决定使用哪种设备。然而,这并不是我在第一篇编辑中试图强调的问题的实际意义。

真正的问题是:“是否有可能直接检测当前设备是否是iPhone X(例如通过某些SDK功能),还是我必须使用间接测量?”

根据目前给出的答案,我假设答案是“不,没有直接的方法。测量是要走的路。”


当前回答

我详细阐述了你对其他人的回答,并在UIDevice上进行了快速扩展。我喜欢快速枚举和“一切有序”&原子化。我已经创建了解决方案,工作在设备和模拟器。

优点: -界面简单,使用方法如UIDevice.current.isIPhoneX - UIDeviceModelType enum让你能够轻松地扩展模型特定的特性和你想在你的应用中使用的常量,例如拐角半径

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

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

其他回答

不要像其他解决方案建议的那样使用屏幕像素大小,这很糟糕,因为它可能会导致未来设备出现假阳性;将不工作,如果UIWindow尚未渲染(AppDelegate),将不工作在景观应用程序,并可以失败在模拟器上,如果比例设置。

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

编辑:更新到支持iPhoneX, iPhoneXS, iPhoneXR, iPhoneXS Max


使用方法:

if (IS_DEVICE_IPHONEX) {
    //do stuff
}

是的,真的。


宏:

复制粘贴到任何地方,我更喜欢@end后的。h文件的底部

#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]);\
})()\
)

另一种可能是,在iOS 11和iOS 12上都能运行,因为iPhone X是唯一一款顶部有凹槽、嵌框为44的手机。这就是我在这里真正发现的:

objective - c:

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

斯威夫特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
}

当然,如果你是在横向,你可能需要检查左右安全区域的嵌入。

Edit: _window是AppDelegate的UIWindow,这个检查在应用didFinishLaunchingWithOptions中完成。

iOS 12的答案更新,以检查顶部>是否为24,而不是顶部> 0。

编辑:在模拟器中,您可以选择硬件,切换通话状态栏。这样做表明,在通话时,iOS 11的iPhone X或iOS 12的iPhone XS上的状态栏高度没有变化。在这两种情况下,改变的只是时间图标,它的背景都是绿色的。这是一张快照:

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

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

}

竖屏尺寸列在这里

更新

这个答案已经过时了,现在iPhone阵容中有更多的X系列,你要么必须在if-else中列出所有这些尺寸,要么检查设备是否有缺口会更容易。大约1.5年前,我从某个地方得到了这个答案/代码。如果我能链接到代码,我会的。

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

根据@saswanb的回复,这是Swift 4的版本:

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

在看完所有的答案后,我最后是这么做的:

解决方案(兼容Swift 4.1)

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你可以检查应用程序是否在模拟器上运行,就像这样:

TARGET_OS_SIMULATOR != 0

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

#if targetEnvironment(simulator)
    return true
#else
    return false
#endif

(旧的方法仍然有效,但这个新方法更经得起未来的考验)