如何检测iPhone 5(宽屏设备)?

302
我刚刚升级到Xcode 4.5 GM,发现现在可以在故事板中将“4英寸Retina”尺寸应用于视图控制器。
现在,如果我想创建一个既能在iPhone 4上运行又能在iPhone 5上运行的应用程序,当然我必须构建两个窗口,但我还必须检测用户是否有3.5英寸或4英寸屏幕的iPhone,然后应用相应的视图。
我该如何做到这一点?

2
你不必两次构建每个“窗口”。只有那些需要完全匹配屏幕尺寸的窗口才需要重新布局。解决方案似乎很明显,只需检查窗口尺寸并基于返回的大小添加一个case决策即可。 - Till
1
嗯,基本上是这样的,但我想以完全不同的方式利用额外的屏幕空间,就像你可以在横向屏幕上做的那样。 - Finn Gaida
请检查此网址:https://dev59.com/FG445IYBdhLWcg3wmLhe#15894286 - ios_av
这个问题是否应该根据新设备进行更新?例如,“如何通过屏幕尺寸检测iOS设备”? - hfossli
24个回答

467
首先,您不应该重建所有视图以适应新屏幕,也不应该为不同的屏幕大小使用不同的视图。使用iOS的自动调整大小功能,使您的视图可以调整并适应任何屏幕大小。这并不难,阅读有关此方面的一些文档即可。它将节省您大量时间。 iOS 6还提供了关于此方面的新功能。请务必在Apple开发者网站上阅读iOS 6 API更改日志。并检查新的iOS 6 AutoLayout功能。 话虽如此,如果您确实需要检测iPhone 5,则可以简单地依赖屏幕尺寸
[ [ UIScreen mainScreen ] bounds ].size.height

iPhone 5的屏幕高度为568。
您可以想象一个宏来简化所有这些:

#define IS_IPHONE_5 ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )

使用fabs和epsilon一起时,可以避免比较浮点数时出现精度误差,正如H2CO3在评论中指出的那样。
因此,从现在开始,您可以在标准if/else语句中使用它:
if( IS_IPHONE_5 )
{}
else
{}

编辑 - 更好的检测

正如一些人所说,这只能检测宽屏幕,而不能检测实际的iPhone 5。

iPod touch的下一个版本可能也会有这样的屏幕,因此我们可以使用另一组宏。

让我们将原始宏IS_WIDESCREEN重命名:

#define IS_WIDESCREEN ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )

让我们添加模型检测宏:

#define IS_IPHONE ( [ [ [ UIDevice currentDevice ] model ] isEqualToString: @"iPhone" ] )
#define IS_IPOD   ( [ [ [ UIDevice currentDevice ] model ] isEqualToString: @"iPod touch" ] )

这样,我们就可以确保拥有一个 iPhone 型号宽屏幕,并重新定义IS_IPHONE_5宏:
#define IS_IPHONE_5 ( IS_IPHONE && IS_WIDESCREEN )

请注意,如@LearnCocos2D所述,如果应用程序未经过iPhone 5屏幕的优化(缺少Default-568h@2x.png图像),则此宏将无法工作,因为在这种情况下屏幕大小仍将是320x480。
我认为这可能不是问题,因为我认为我们没有理由在非优化的应用程序中检测iPhone 5。
重要提示 - iOS 8支持
在iOS 8上,UIScreen类的bounds属性现在反映了设备方向。 因此,以前的代码显然无法直接使用。
为了解决这个问题,你可以简单地使用新的nativeBounds属性,而不是bounds,因为它不会随着方向改变,而且基于纵向模式。 请注意,nativeBounds的尺寸是以像素为单位测量的,因此对于iPhone 5,高度将为1136,而不是568。
如果你也需要支持 iOS 7 或更低版本,请确保使用特性检测,因为在 iOS 8 之前调用 nativeBounds 会导致应用程序崩溃。
if( [ [ UIScreen mainScreen ] respondsToSelector: @selector( nativeBounds ) ] )
{
    /* Detect using nativeBounds - iOS 8 and greater */
}
else
{
    /* Detect using bounds - iOS 7 and lower */
}

您可以按照以下方式调整先前的宏:
#define IS_WIDESCREEN_IOS7 ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )
#define IS_WIDESCREEN_IOS8 ( fabs( ( double )[ [ UIScreen mainScreen ] nativeBounds ].size.height - ( double )1136 ) < DBL_EPSILON )
#define IS_WIDESCREEN      ( ( [ [ UIScreen mainScreen ] respondsToSelector: @selector( nativeBounds ) ] ) ? IS_WIDESCREEN_IOS8 : IS_WIDESCREEN_IOS7 )

显然,如果您需要检测iPhone 6或6 Plus,请使用相应的屏幕尺寸。

7
这是错误的,你必须使用 #define IS_IPHONE_5 ( [ [ UIScreen mainScreen ] bounds ].size.height == 568 ) - Fabian Kreiser
28
请注意,这里不必与 DBL_EPSILON 进行比较,并且 == 比较 不会 失败:只有在浮点值不能表示为一个精确的数字时(例如 1.0/3.0*3.0),才需要使用此方法进行差值比较。阅读这篇文章以获取更多信息;) - AliSoftware
5
我可以补充一下:如果你想在模拟器上运行,请使用以下代码:#define IS_IPHONE ( ( [ [ [ UIDevice currentDevice ] model ] isEqualToString: @"iPhone" ] ) || ( [ [ [ UIDevice currentDevice ] model ] isEqualToString: @"iPhone Simulator" ] ) ) - david
31
这个答案很疯狂。关于不建议使用“==”来比较这种特定类型的浮点数(实际上是整数,如果您知道苹果应该知道这一点),这是无意义和过于复杂化的。此外,我认为最好使用UI_USER_INTERFACE_IDIOM()来检测iPhone,因为它在设备和模拟器上都可以正常工作(而且可能比UIDevice方法更快)。这只需要良好地工作,并且阅读起来更简单:#define IS_IPHONE5 (UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPhone && [UIScreen mainScreen].bounds.size.height==568)。 - Ricardo Sanchez-Saez
2
其他答案中有一些比这个答案使用的所有东西更高效和优雅。我在应用商店上有几个应用程序正在使用其他答案中的技术,它们都运行良好,代码更简单、更清晰。 - johnbakers
显示剩余27条评论

232

适用于任何SDK和操作系统的组合:

Swift

新增iPad类型。 iPad 2和iPad mini是非Retina iPad。而iPad Mini 2及以上,iPad 3, 4, iPad Air, Air 2, Air 3和iPad Pro 9.7 的逻辑分辨率相同为1024。iPad Pro 最大长度为1366。 参考资料

import UIKit

public enum DisplayType {
    case unknown
    case iphone4
    case iphone5
    case iphone6
    case iphone6plus
    case iPadNonRetina
    case iPad
    case iPadProBig
    static let iphone7 = iphone6
    static let iphone7plus = iphone6plus
}

public final class Display {
    class var width:CGFloat { return UIScreen.main.bounds.size.width }
    class var height:CGFloat { return UIScreen.main.bounds.size.height }
    class var maxLength:CGFloat { return max(width, height) }
    class var minLength:CGFloat { return min(width, height) }
    class var zoomed:Bool { return UIScreen.main.nativeScale >= UIScreen.main.scale }
    class var retina:Bool { return UIScreen.main.scale >= 2.0 }
    class var phone:Bool { return UIDevice.current.userInterfaceIdiom == .phone }
    class var pad:Bool { return UIDevice.current.userInterfaceIdiom == .pad }
    class var carplay:Bool { return UIDevice.current.userInterfaceIdiom == .carPlay }
    class var tv:Bool { return UIDevice.current.userInterfaceIdiom == .tv }
    class var typeIsLike:DisplayType {
        if phone && maxLength < 568 {
            return .iphone4
        }
        else if phone && maxLength == 568 {
                return .iphone5
        }
        else if phone && maxLength == 667 {
            return .iphone6
        }
        else if phone && maxLength == 736 {
            return .iphone6plus
        }
        else if pad && !retina {
            return .iPadNonRetina
        }
        else if pad && retina && maxLength == 1024 {
            return .iPad
        }
        else if pad && maxLength == 1366 {
            return .iPadProBig
        }
        return .unknown
    }
}

在实际操作中查看示例 https://gist.github.com/hfossli/bc93d924649de881ee2882457f14e346

注意:如果例如iPhone 6处于缩放模式,则UI是iPhone 5的缩放版本。这些函数不确定设备类型,而是显示模式,因此在此示例中,iPhone 5是期望的结果。

Objective-C

#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_RETINA ([[UIScreen mainScreen] scale] >= 2.0)

#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
#define SCREEN_HEIGHT ([[UIScreen mainScreen] bounds].size.height)
#define SCREEN_MAX_LENGTH (MAX(SCREEN_WIDTH, SCREEN_HEIGHT))
#define SCREEN_MIN_LENGTH (MIN(SCREEN_WIDTH, SCREEN_HEIGHT))
#define IS_ZOOMED (IS_IPHONE && SCREEN_MAX_LENGTH == 736.0)

#define IS_IPHONE_4_OR_LESS (IS_IPHONE && SCREEN_MAX_LENGTH < 568.0)
#define IS_IPHONE_5 (IS_IPHONE && SCREEN_MAX_LENGTH == 568.0)
#define IS_IPHONE_6 (IS_IPHONE && SCREEN_MAX_LENGTH == 667.0)
#define IS_IPHONE_6P (IS_IPHONE && SCREEN_MAX_LENGTH == 736.0)

使用方法:http://pastie.org/9687735

注意:例如,如果iPhone 6处于缩放模式,则UI是iPhone 5的放大版本。这些功能不确定设备类型,而是显示模式,因此在此示例中期望的结果是iPhone 5。


1
iPhone 5会报告常规的480x320屏幕尺寸,没有新的默认图像。对我来说,这是期望的行为。 - hfossli
3
一个可能有用的补充是 #define IS_RETINA ([[UIScreen mainScreen] scale] == 2.0),它将有助于区分 iPhone4 和 iPhone5 以及 iPad Retina 和非Retina设备。 - bshirley
1
我不同意。我认为“宽屏”术语应该被遗弃,因为它很快就会过时。 - hfossli
1
@MattParkins 我建议使用更健壮的模型检查:https://dev59.com/vGYr5IYBdhLWcg3w1NZY. - hfossli
1
@hfossli 感谢你解释了iOS 8的旋转问题。再给iPad Pro添加一个宏定义:#define IS_IPAD_PRO (IS_IPAD && SCREEN_MAX_LENGTH == 1366.0) :-) - avdyushin
显示剩余4条评论

69

非常简单的解决方案

if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
    CGSize result = [[UIScreen mainScreen] bounds].size;
    if(result.height == 480)
    {
        // iPhone Classic
    }
    if(result.height == 568)
    {
        // iPhone 5
    }
}

1
哈哈,简短而简单,我也做了同样的事情 :) 为保持开销低而点赞!将东西放在宏中并不是一项挑战... - benjamin.ludwig
2
不将事物放入宏或函数中容易导致不符合DRY原则...从你需要进行多次检查的那一刻开始... - hfossli
是的,但是像上面展示的那样定义宏更加方便和容易,您不需要每次都复制粘贴写这个if...。 - Resty
谢谢,你救了我的命 :D,但我不知道为什么宏定义:#define IS_IPHONE_5 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 568.0) 在模拟器iOS 7.1上无法工作,在此之前我还在XCode 4.6上工作。天哪,iOS 7.1和Xcode 5。 - Linh Nguyen
为了适应iPhone 6和6 Plus的屏幕尺寸更新了下面的答案。 - Sam B

28

现在我们需要考虑 iPhone 6 和 6Plus 的屏幕尺寸。这是更新后的答案。

if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
    //its iPhone. Find out which one?

    CGSize result = [[UIScreen mainScreen] bounds].size;
    if(result.height == 480)
    {
        // iPhone Classic
    }
    else if(result.height == 568)
    {
        // iPhone 5
    }
    else if(result.height == 667)
    {
        // iPhone 6
    }
   else if(result.height == 736)
    {
        // iPhone 6 Plus
    }
}
else
{
     //its iPad
}

一些有用的信息

iPhone 6 Plus   736x414 points  2208x1242 pixels    3x scale    1920x1080 physical pixels   401 physical ppi    5.5"
iPhone 6        667x375 points  1334x750 pixels     2x scale    1334x750 physical pixels    326 physical ppi    4.7"
iPhone 5        568x320 points  1136x640 pixels     2x scale    1136x640 physical pixels    326 physical ppi    4.0"
iPhone 4        480x320 points  960x640 pixels      2x scale    960x640 physical pixels     326 physical ppi    3.5"
iPhone 3GS      480x320 points  480x320 pixels      1x scale    480x320 physical pixels     163 physical ppi    3.5"

它对我来说根本不起作用 iPhone 5被识别为4 iPhone 6+根本没有被识别哦,我明白了,我处于横向模式,应该将高度与宽度互换 :) - Coldsteel48
如果您的应用程序处于横向模式,请确保将result.height更改为result.width。 - Sam B
嗯...在iPhone 4(iOS 6.0)上它没有交换 :( 可能是iOS 6.0的问题还是iPhone 4的问题? - Coldsteel48
好的,我检查了一下,视图只在iOS 8及以上版本中交换。 - Coldsteel48
iPhone 6 的高度为 568。 - MD SHAHIDUL ISLAM

15

我已经将Macmade的宏转换成了一个C函数,并为其命名,因为它检测到了宽屏可用性,但并不一定是iPhone 5。

如果项目中不包括Default-568h@2x.png,宏也无法检测是否在iPhone 5上运行。没有新的默认图像,iPhone 5 将报告常规的480x320点屏幕尺寸。因此,检查不仅仅是检测宽屏可用性,还需要检测启用了宽屏模式

BOOL isWidescreenEnabled()
{
    return (BOOL)(fabs((double)[UIScreen mainScreen].bounds.size.height - 
                                               (double)568) < DBL_EPSILON);
}

出于性能原因,我仍然更喜欢使用宏。请查看我答案的编辑。它还会检查模型。 - Macmade
1
你也是对的,没有添加新的默认图像,iPhone 5会报告一个普通的480x320屏幕尺寸。但是我认为在一个非优化的应用中检测iPhone 5是没有意义的。:) - Macmade
@Macmade确实,这没有意义,但是如果检测不起作用,记住这一点很好。此外,函数可以进行inline操作。编译器优化器认为这是一个好主意并且可以知道它是允许的地方,它们也将被内联(例如,函数在同一模块中)。通过函数实现这样的东西有时可能会带来额外的类型检查。 - Ivan Vučica
4
关于性能的问题是,为什么你会在渲染循环中运行这个检查数千次?否则,性能不是问题,重要的是清晰度和避免副作用。 - CodeSmile
我给你点赞是因为我喜欢使用单独的函数而不是宏,但我必须指出这并不完全正确。要检测宽屏,请不要查看屏幕的高度。相反,查看宽高比,只有当宽高比大于或等于16:9时才返回true。 - Todd Lehman

11

这是我们的代码,在设备或模拟器上,适用于 iPhone4、iPhone5、iPad、iPhone6 和 iPhone6P 的 iOS7 / iOS8 系统通过测试:


#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) // iPhone and       iPod touch style UI

#define IS_IPHONE_5_IOS7 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 568.0f)
#define IS_IPHONE_6_IOS7 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 667.0f)
#define IS_IPHONE_6P_IOS7 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height == 736.0f)
#define IS_IPHONE_4_AND_OLDER_IOS7 (IS_IPHONE && [[UIScreen mainScreen] bounds].size.height < 568.0f)

#define IS_IPHONE_5_IOS8 (IS_IPHONE && ([[UIScreen mainScreen] nativeBounds].size.height/[[UIScreen mainScreen] nativeScale]) == 568.0f)
#define IS_IPHONE_6_IOS8 (IS_IPHONE && ([[UIScreen mainScreen] nativeBounds].size.height/[[UIScreen mainScreen] nativeScale]) == 667.0f)
#define IS_IPHONE_6P_IOS8 (IS_IPHONE && ([[UIScreen mainScreen] nativeBounds].size.height/[[UIScreen mainScreen] nativeScale]) == 736.0f)
#define IS_IPHONE_4_AND_OLDER_IOS8 (IS_IPHONE && ([[UIScreen mainScreen] nativeBounds].size.height/[[UIScreen mainScreen] nativeScale]) < 568.0f)

#define IS_IPHONE_5 ( ( [ [ UIScreen mainScreen ] respondsToSelector: @selector( nativeBounds ) ] ) ? IS_IPHONE_5_IOS8 : IS_IPHONE_5_IOS7 )
#define IS_IPHONE_6 ( ( [ [ UIScreen mainScreen ] respondsToSelector: @selector( nativeBounds ) ] ) ? IS_IPHONE_6_IOS8 : IS_IPHONE_6_IOS7 )
#define IS_IPHONE_6P ( ( [ [ UIScreen mainScreen ] respondsToSelector: @selector( nativeBounds ) ] ) ? IS_IPHONE_6P_IOS8 : IS_IPHONE_6P_IOS7 )
#define IS_IPHONE_4_AND_OLDER ( ( [ [ UIScreen mainScreen ] respondsToSelector: @selector( nativeBounds ) ] ) ? IS_IPHONE_4_AND_OLDER_IOS8 : IS_IPHONE_4_AND_OLDER_IOS7 )

我正在 iPhone 6P 上进行测试,但我的 if 语句却进入了 IS_IPHONE_5 条件中?这怎么可能,你的代码看起来没问题啊?我使用了简单的 if/else 直接复制粘贴,我知道我的手机是运行 iOS 8.3 的 iPhone 6 Plus。 - whyoz

7
我使用了hfossli的答案并将其翻译成了Swift。
let IS_IPAD = UIDevice.currentDevice().userInterfaceIdiom == .Pad
let IS_IPHONE = UIDevice.currentDevice().userInterfaceIdiom == .Phone
let IS_RETINA = UIScreen.mainScreen().scale >= 2.0

let SCREEN_WIDTH = UIScreen.mainScreen().bounds.size.width
let SCREEN_HEIGHT = UIScreen.mainScreen().bounds.size.height
let SCREEN_MAX_LENGTH = max(SCREEN_WIDTH, SCREEN_HEIGHT)
let SCREEN_MIN_LENGTH = min(SCREEN_WIDTH, SCREEN_HEIGHT)

let IS_IPHONE_4_OR_LESS = (IS_IPHONE && SCREEN_MAX_LENGTH < 568.0)
let IS_IPHONE_5 = (IS_IPHONE && SCREEN_MAX_LENGTH == 568.0)
let IS_IPHONE_6 = (IS_IPHONE && SCREEN_MAX_LENGTH == 667.0)
let IS_IPHONE_6P = (IS_IPHONE && SCREEN_MAX_LENGTH == 736.0)

6
这是我的Cocos2d项目的宏,其他应用程序应该是相同的。
#define WIDTH_IPAD 1024
#define WIDTH_IPHONE_5 568
#define WIDTH_IPHONE_4 480
#define HEIGHT_IPAD 768
#define HEIGHT_IPHONE 320

#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

//width is height!
#define IS_IPHONE_5 ( [ [ UIScreen mainScreen ] bounds ].size.height == WIDTH_IPHONE_5 )
#define IS_IPHONE_4 ( [ [ UIScreen mainScreen ] bounds ].size.height == WIDTH_IPHONE_4 )

#define cp_ph4(__X__, __Y__) ccp(cx_ph4(__X__), cy_ph4(__Y__))
#define cx_ph4(__X__) (IS_IPAD ? (__X__ * WIDTH_IPAD / WIDTH_IPHONE_4) : (IS_IPHONE_5 ? (__X__ * WIDTH_IPHONE_5 / WIDTH_IPHONE_4) : (__X__)))
#define cy_ph4(__Y__) (IS_IPAD ? (__Y__ * HEIGHT_IPAD / HEIGHT_IPHONE) : (__Y__))

#define cp_pad(__X__, __Y__) ccp(cx_pad(__X__), cy_pad(__Y__))
#define cx_pad(__X__) (IS_IPAD ? (__X__) : (IS_IPHONE_5 ? (__X__ * WIDTH_IPHONE_5 / WIDTH_IPAD) : (__X__ * WIDTH_IPHONE_4 / WIDTH_IPAD)))
#define cy_pad(__Y__) (IS_IPAD ? (__Y__) : (__Y__ * HEIGHT_IPHONE / HEIGHT_IPAD))

5
if ((int)[[UIScreen mainScreen] bounds].size.height == 568)
{
    // This is iPhone 5 screen
} else {
    // This is iPhone 4 screen
}

3
在Swift,iOS 8+项目中,我喜欢对UIScreen进行扩展,如下所示:
extension UIScreen {

    var isPhone4: Bool {
        return self.nativeBounds.size.height == 960;
    }

    var isPhone5: Bool {
        return self.nativeBounds.size.height == 1136;
    }

    var isPhone6: Bool {
        return self.nativeBounds.size.height == 1334;
    }

    var isPhone6Plus: Bool {
        return self.nativeBounds.size.height == 2208;
    }

}

(注意:nativeBounds以像素为单位)。

然后代码将如下所示:

if UIScreen.mainScreen().isPhone4 {
    // do smth on the smallest screen
}

因此,代码明确表示这是针对主屏幕的检查,而不是设备型号。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接