我的 iOS 应用程序为 UINavigationBar
使用自定义高度,这会导致新 iPhone X 出现一些问题。
如果应用程序在 iPhone X 上运行,是否有人已经知道如何以编程方式(在 Objective-C 中)可靠地检测?
编辑:
当然可以检查屏幕的大小,但是,我想知道是否有像 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 功能)还是我必须使用间接测量”?
根据到目前为止给出的答案,我认为答案是“不,没有直接的方法。测量是要走的路”。
根据您的问题,答案是否定的。没有直接的方法。有关更多信息,您可以在此处获取信息:
如何在 iOS 上获取设备品牌和型号?
和
如何以编程方式快速检查 iphone 4 和 iphone 5 的屏幕尺寸
iPhone X 高度为 2436 像素
从 Device Screen Sizes and resolutions:
https://i.stack.imgur.com/JcCJp.png
从 Device Screen Sizes and Orientations:
https://i.stack.imgur.com/d0x04.png
Swift 3 及更高版本:
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")
}
}
目标-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");
}
}
根据您的问题如下:
或者将 screenSize.height
用作 float 812.0f
而不是 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");
}
有关更多信息,您可以参考 iOS 人机界面指南中的以下页面:
适应性和布局 - 视觉设计 - iOS - 人机界面指南
迅速:
使用 topNotch
检测:
如果有人考虑使用 notch 来检测 iPhoneX,请注意在“风景”上对所有 iPhone 都是一样的。
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
}
目标-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;
}
更新:
不要使用 userInterfaceIdiom
属性来识别设备类型,如 documentation for userInterfaceIdiom 所述:
对于通用应用程序,您可以使用此属性为特定类型的设备定制应用程序的行为。例如,iPhone 和 iPad 设备具有不同的屏幕尺寸,因此您可能希望根据当前设备的类型创建不同的视图和控件。
也就是说,这个属性只是用来标识正在运行的应用程序的视图样式。但是,iPhone 应用程序(不是通用的)可以通过 App Store 安装在 iPad 设备上,在这种情况下,userInterfaceIdiom
也将返回 UIUserInterfaceIdiomPhone
。
正确的方法是通过 uname
获取机器名称。检查以下内容以了解详细信息:
如何在 iOS 上获取设备品牌和型号?
另一种可能性,它适用于 iOS 11 和 iOS 12,因为 iPhone X 是唯一一个顶部有一个凹口和 44 插图的设备。这就是我在这里真正检测到的:
目标-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
}
当然,如果您处于横向,您可能需要检查左右安全区域插图。
编辑:_window 是 AppDelegate 的 UIWindow,此检查在应用程序 didFinishLaunchingWithOptions 中完成。
为 iOS 12 更新了答案以检查 top > 24 而不是 top > 0。
编辑:在模拟器中,您可以转到硬件,切换通话状态栏。这样做向我表明,在进行通话时,iOS 11 上的 iPhone X 或 iPhone XS iOS 12 上的状态栏高度不会改变。所有改变的只是时间图标,在这两种情况下都变成绿色背景。这是一个快照:
https://i.stack.imgur.com/ZVVzv.png
if _window.safeAreaInsets != UIEdgeInsets.zero
来允许任何设备方向
.top
,则 safeAreaInsets.bottom
在 iPhone X 上为 34,在其他设备上为 0
您应根据实际需要对 iPhone X 进行不同的检测。
用于处理一流的(状态栏、导航栏)等。
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
}
用于处理底部主页指示器(标签栏)等。
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
}
用于背景大小、全屏功能等。
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
混合注意:此方法需要具有 LaunchScreen 故事板或适当的 LaunchImages
背景比例,滚动功能等。
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
}
注意:此方法需要具有 LaunchScreen 故事板或适当的 LaunchImages
用于分析、统计、跟踪等。
获取机器标识符并将其与记录的值进行比较:
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 包含在您的分析中:
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”开头的型号:
return model == "iPhone10,3" || model == "iPhone10,6" || model.starts(with: "iPhone11,")
面部识别支持
import LocalAuthentication
/// will fail if user denies canEvaluatePolicy(_:error:)
class var canUseFaceID: Bool {
if #available(iOS 11.0, *) {
return LAContext().biometryType == .typeFaceID
}
return false
}
return LAContext().biometryType == .typeFaceID
也能正常工作,但它对我不起作用,它仍然返回 .none
model == "iPhone10,3" || model == "iPhone10,6"
),如果 canUseFaceID
返回 false,则表示它被用户拒绝。
您可以这样做以根据尺寸检测 iPhone X 设备。
迅速
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
//iPhone X
}
目标 - C
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && UIScreen.mainScreen.nativeBounds.size.height == 2436) {
//iPhone X
}
https://i.stack.imgur.com/xX17z.png
但,
这还不够。如果 Apple 宣布推出与 iPhone X 相同尺寸的下一代 iPhone,那么最好的方法是使用硬件字符串来检测设备。
对于较新的设备硬件字符串如下。
iPhone 8 - iPhone10,1 或 iPhone 10,4
iPhone 8 Plus - iPhone10,2 或 iPhone 10,5
iPhone X - iPhone10,3 或 iPhone10,6
[UIDevice currentDevice]
而不是 [[UIDevice alloc] init]
查看 the device model / machine name,不要直接在代码中使用点数/像素数,它是 hard code,对设备硬件没有意义,设备型号是匹配设备类型的唯一唯一标识符强>。
#import <sys/utsname.h>
NSString* deviceName()
{
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine
encoding:NSUTF8StringEncoding];
}
结果:
@"iPhone10,3" on iPhone X (CDMA)
@"iPhone10,6" on iPhone X (GSM)
请参阅this answer。
完整代码实现:
#import <sys/utsname.h>
NSString * GetDeviceModel(void)
{
static dispatch_once_t onceToken;
static NSString *strModelID = nil;
dispatch_once(&onceToken, ^{
#if TARGET_IPHONE_SIMULATOR
strModelID = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"];
#else
struct utsname systemInfo;
uname(&systemInfo);
strModelID = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
#endif
});
return strModelID;
}
// See the `Hardware strings` in https://en.wikipedia.org/wiki/List_of_iOS_devices
BOOL IsiPhoneX(void)
{
NSString *strModelID = GetDeviceModel();
return [strModelID isEqualToString:@"iPhone10,3"] || [strModelID isEqualToString:@"iPhone10,6"];
}
BOOL IsNotchiPhone(void)
{
NSArray<NSString *> *notchiModels = @[
@"iPhone10,3", @"iPhone10,6", // iPhone X
@"iPhone11,2", @"iPhone11,4", @"iPhone11,6", // iPhone XS (Max)
@"iPhone11,8", // iPhone XR
@"iPhone12,1", @"iPhone12,3", @"iPhone12,5", // iPhone 11 (Pro (Max))
@"iPhone13,1", @"iPhone13,2", @"iPhone13,3", @"iPhone13,4", // iPhone 12 ([mini]|[Pro (Max)])
];
return [notchiModels containsObject:GetDeviceModel()];
}
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_4 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 480.0)
#define IS_IPHONE_5 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 568.0)
#define IS_IPHONE_6 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 667.0)
#define IS_IPHONE_6PLUS (IS_IPHONE && [[UIScreen mainScreen] nativeScale] == 3.0f)
#define IS_IPHONE_6_PLUS (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 736.0)
#define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0)
定义 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"]
注意:- 小心,它仅适用于纵向
nativeScale
和 3.0
,对吗?
在查看了所有答案之后,这就是我最终要做的事情:
解决方案(兼容 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
}
}
利用
if UIDevice.isIphoneX {
// is iPhoneX
} else {
// is not iPhoneX
}
笔记
在 Swift 4.1 之前,您可以检查应用程序是否在模拟器上运行,如下所示:
TARGET_OS_SIMULATOR != 0
从 Swift 4.1 起,您可以使用 Target environment platform condition 检查应用程序是否在模拟器上运行:
#if targetEnvironment(simulator)
return true
#else
return false
#endif
(旧方法仍然有效,但这种新方法更适合未来)
所有这些基于尺寸的答案都容易受到未来设备上不正确行为的影响。他们今天可以工作,但如果明年有一款尺寸相同但在玻璃下有摄像头等的 iPhone 没有“缺口”怎么办?如果唯一的选择是更新应用程序,那么这对您和您的客户来说都是一个糟糕的解决方案。
您还可以检查硬件型号字符串,如“iPhone10,1”,但这是有问题的,因为有时 Apple 会为全球不同的运营商发布不同的型号。
正确的方法是重新设计顶部布局,或者解决您在自定义导航栏高度方面遇到的问题(这就是我要关注的问题)。但是,如果你决定不做这些事情中的任何一个,请意识到你所做的任何事情都是为了让它在今天正常工作,你需要在某个时候纠正它,也许是多次,以保持这些黑客行为在职的。
支持 iPhone 12 的 SWIFT 4/5 可重复使用扩展
extension UIDevice {
enum `Type` {
case iPhone_5_5S_5C_SE1
case iPhone_6_6S_7_8_SE2
case iPhone_6_6S_7_8_PLUS
case iPhone_X_XS_12mini
case iPhone_XR_11
case iPhone_XS_11Pro_Max
case iPhone_12_Pro
case iPhone_12_Pro_Max
}
var hasHomeButton: Bool {
switch type {
case . iPhone_X_XS_12mini, . iPhone_XR_11, .iPhone_XS_11Pro_Max, . iPhone_XS_11Pro_Max, .iPhone_12_Pro, .iPhone_12_Pro_Max:
return false
default:
return true
}
}
var type: Type {
if UI_USER_INTERFACE_IDIOM() == .phone {
switch UIScreen.main.nativeBounds.height {
case 1136:
return .iPhone_5_5S_5C_SE1
case 1334:
return .iPhone_6_6S_7_8_SE2
case 1920, 2208:
return .iPhone_6_6S_7_8_PLUS
case 2436:
return .iPhone_X_XS_12mini
case 2532:
return .iPhone_12_Pro
case 2688:
return .iPhone_XS_11Pro_Max
case 2778:
return .iPhone_12_Pro_Max
case 1792:
return .iPhone_XR_11
default:
assertionFailure("Unknown phone device detected!")
return .iPhone_6_6S_7_8_SE2
}
} else {
assertionFailure("Unknown idiom device detected!")
return .iPhone_6_6S_7_8_SE2
}
}
}
UIDevice.current.hasHomeButton
斯威夫特 4+ 答案
iPhone X、XR、XS、XSMAX、11 Pro、11 Pro Max:
注意:需要真机进行测试
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
}
}
对的,这是可能的。下载 UIDevice-Hardware extension(或通过 CocoaPod 'UIDevice-Hardware' 安装),然后使用:
NSString* modelID = [[[UIDevice currentDevice] modelIdentifier];
BOOL isIphoneX = [modelID isEqualToString:@"iPhone10,3"] || [modelID isEqualToString:@"iPhone10,6"];
请注意,这在模拟器中不起作用,仅在实际设备上起作用。
ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"]
从 Xcode 获取实际值。
我知道这只是一个 Swift 解决方案,但它可以帮助某人。
我在每个项目中都有 globals.swift
,其中包含一些代码来简化我的生活,我经常添加的东西之一是 ScreenSize
和 hasNotch
,以便轻松检测手机类型和尺寸:
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
}
然后使用它:
if hasNotch {
print("This executes on all phones with a notch")
}
if DeviceType.iPhoneX { //do something for iPhone X notch }else{ // don’t do anything about notch }
根据@saswanb 的回复,这是 Swift 4 版本:
var iphoneX = false
if #available(iOS 11.0, *) {
if ((UIApplication.shared.keyWindow?.safeAreaInsets.top)! > CGFloat(0.0)) {
iphoneX = true
}
}
viewDidAppear
之前,keyWindow
是 nil
出于一个原因,使用 height
的所有答案只是故事的一半。如果您要在设备方向为 landscapeLeft
或 landscapeRight
时进行类似检查,则检查将失败,因为 height
已与 width
交换。
这就是为什么我的解决方案在 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
}
}
不要像其他解决方案建议的那样使用屏幕像素大小,这很糟糕,因为它可能导致未来设备的误报;如果 UIWindow 尚未渲染 (AppDelegate) 将无法工作,将无法在横向应用程序中工作,并且如果设置了比例,则可能会在模拟器上失败。
相反,我为此目的制作了一个宏,它非常易于使用,并且依靠硬件标志来防止上述问题。
编辑:更新以支持 iPhoneX、iPhone XS、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]);\
})()\
)
if (@available(iOS 11.0, *)) { [UIApplication sharedApplication].keyWindow.safeAreaInsets.top }
您不应假设 Apple 发布的唯一具有不同 UINavigationBar 高度的设备将是 iPhone X。尝试使用更通用的解决方案来解决此问题。如果您希望栏始终比其默认高度大 20 像素,则您的代码应将 20 像素添加到栏的高度,而不是将其设置为 64 像素(44 像素 + 20 像素)。
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
}
斯威夫特 3 + 4:
不需要任何设备大小的像素值
//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
}
}
例子:
if UIApplication.isDeviceWithSafeArea {
//e.g. change the frame size height of your UITabBar
}
仅在纵向中,我使用视图框架的宽度和高度来检查:
override func viewDidLoad() {
super.viewDidLoad()
// iPhone Xr: -414 x 896
// iPhone Xs Max: -414 x 896
// iPhone X, Xs: -375 x 812
if view.frame.width == 414 && view.frame.height == 896 || view.frame.width == 375 && view.frame.height == 812 {
print("iPhone X")
} else {
print("not iPhone X")
}
}
The portrait screen dimensions are listed here
https://i.stack.imgur.com/TmXS3.png
更新
这个答案很旧,现在 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")
}
}
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPHONE_X (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 812.0f)
- (BOOL)isIphoneX {
if (@available(iOS 11.0, *)) {
UIWindow *window = UIApplication.sharedApplication.keyWindow;
CGFloat topPadding = window.safeAreaInsets.top;
if(topPadding>0) {
return YES;
}
else {
return NO;
}
}
else {
return NO;
}
}
通常,程序员需要它来限制顶部或底部,所以这些方法可以帮助
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
然后只需将这些附加内容添加到顶部或底部约束
对于那些获得 2001px 而不是 2436px 的原生边界高度的人(比如我),这是因为您在 iOS 11(Xcode 8 而不是 Xcode 9)之前使用较旧的 SDK 构建了您的应用程序。使用较旧的 SDK,iOS 将在 iPhone X 上以“黑框”方式显示应用程序,而不是将屏幕从边缘延伸到边缘,超出顶部的“传感器凹槽”。这会减小屏幕尺寸,这就是该属性返回 2001 而不是 2436 的原因。
如果您只对设备检测感兴趣,最简单的解决方案是只检查两种尺寸。我使用这种方法来检测 FaceID,同时使用没有指定生物特征类型的 ENUM 值的旧 Xcode SDK 构建。在这种情况下,使用屏幕高度进行设备检测似乎是无需更新 Xcode 即可了解设备是否具有 FaceID 和 TouchID 的最佳方式。
我正在使用 Peter Kreinz's code (因为它很干净并且做了我需要的)但后来我意识到它仅在设备处于纵向时才有效(因为显然顶部填充将在顶部)所以我创建了一个扩展来处理所有方向及其各自的填充,而不是在屏幕尺寸上中继:
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
}
}
在您的呼叫站点上,您只需:
let res = UIDevice.current.isIphoneX
我详细阐述了您对其他人的回答,并在 UIDevice 上进行了快速扩展。我喜欢快速枚举和“一切井井有条”和原子化。我创建了适用于设备和模拟器的解决方案。
优点: - 简单的界面,使用例如 UIDevice.current.isIPhoneX
- UIDeviceModelType
枚举使您能够轻松扩展您想在应用程序中使用的模型特定功能和常量,例如cornerRadius
缺点: - 它是特定于模型的解决方案,而不是特定于分辨率 - 例如,如果 Apple 将生产具有相同规格的另一个模型,这将无法正常工作,您需要添加另一个模型才能使其工作 => 您需要更新您的应用程序。
extension UIDevice {
enum UIDeviceModelType : Equatable {
///iPhoneX
case iPhoneX
///Other models
case other(model: String)
static func type(from model: String) -> UIDeviceModelType {
switch model {
case "iPhone10,3", "iPhone10,6":
return .iPhoneX
default:
return .other(model: model)
}
}
static func ==(lhs: UIDeviceModelType, rhs: UIDeviceModelType) -> Bool {
switch (lhs, rhs) {
case (.iPhoneX, .iPhoneX):
return true
case (.other(let modelOne), .other(let modelTwo)):
return modelOne == modelTwo
default:
return false
}
}
}
var simulatorModel: String? {
guard TARGET_OS_SIMULATOR != 0 else {
return nil
}
return ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"]
}
var hardwareModel: String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let model = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return model
}
var modelType: UIDeviceModelType {
let model = self.simulatorModel ?? self.hardwareModel
return UIDeviceModelType.type(from: model)
}
var isIPhoneX: Bool {
return modelType == .iPhoneX
}
}
Mirror
,不如使用 Cloud9999Strife 答案(以及我的答案)中的 sysctlbyname
会更快。
我依靠状态栏框架高度来检测它是否是 iPhone X:
if UIApplication.shared.statusBarFrame.height >= CGFloat(44) {
// It is an iPhone X
}
这是应用程序联合国肖像。您还可以根据设备方向检查尺寸。此外,在其他 iPhone 上,状态栏可能会被隐藏,因此框架高度为 0
。在 iPhone X 上,状态栏从不隐藏。
controller
中隐藏 iPhoneX 状态栏:- (BOOL)prefersStatusBarHidden { return YES; }
那么状态栏的高度为 0。
或者,您可以查看“DeviceKit”窗格。安装后,您需要做的就是检查设备:
import DeviceKit
let device = Device()
if device == .iPhoneX {
// place your code here
}
我认为 Apple 不希望我们手动检查设备是否有“缺口”或“主页指示器”,但有效的代码是:
-(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;
}
其他一些帖子不起作用。例如,iPhone 6S 在竖屏模式下带有“通话状态栏”(绿色栏),顶部有一个大保险箱。使用我的代码,所有案例都被占用(即使设备以纵向或横向启动)
[[[UIApplication sharedApplication] delegate] window].safeAreaInsets
而不是仅将此类值保存在变量中并使用它?
2019 年 11 月:
这是我在所有生产项目中使用的。请注意,这个要点很长。
这不使用宽度或高度的计算,而是:它检查设备字符串模型。不会因为使用任何私有/未记录的 API 而导致您的构建被 Apple 拒绝。适用于模拟器 💯 import UIKit class DeviceUtility { /// 确定用户的当前设备是否为 iPhoneX 类型/变体。 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: - iPod 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 // #-#-#-#-#- #-#-#-#-#-#-#-#-#-# 公共扩展 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 } }
用法:让插入:CGFloat = DeviceUtility.isIphoneXType ? 50.0 : 40.0
我最近不得不解决同样的问题。虽然这个问题得到了明确的回答(“否”),但这可能会帮助其他需要 iPhone X 特定布局行为的人。
我对设备是否是 iPhone X 并不真正感兴趣。我对设备是否有缺口显示屏感兴趣。
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 和更早版本上方便地访问安全区域插图。
@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
}
}
}
不定期副业成功案例分享
userInterfaceIdiom
也会返回UIUserInterfaceIdiomPhone
.这个答案是错误的。