检测Retina显示屏

226

iOS SDK提供了一种简单的方法来检查当前设备是否具有高分辨率显示(retina)吗?

我现在发现最好的方法是:

    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] == YES && [[UIScreen mainScreen] scale] == 2.00) {
         // RETINA DISPLAY
    }

好奇问一下,当您检测到显示器时,除了显示更大版本的艺术作品之外,您还在做什么? - Michael Behan
4
可能是与如何区分 iPhone4 和 iPhone 3?类似的问题。 - Kendall Helmstetter Gelner
我有一个TTImageView(请参阅Three20框架),我想提供图像的高分辨率URL。 - Pierre Valade
1
这个问题对我也很有用,因为我已经下载了呈现为 UI 的图像,在所有 4 种显示尺寸上都有可用的大小,并且只希望用户下载适当的尺寸。 - Pedro
@mbehan:在我的情况下,我想要自定义单元格分隔符,在视网膜和非视网膜屏幕上都为1像素(就像本机分隔符一样)。将厚度设置为1像素会在视网膜显示器上呈现为2像素(显然)。 - user3099609
不要忘记,在iPhone6和6 Plus上,您需要测试比例“>= 2.0”,而不仅仅是“== 2.0”。 - James
14个回答

295
为了可靠地检测所有iOS设备上的Retina显示屏,您需要检查设备是否正在运行iOS4+,以及[UIScreen mainScreen].scale属性是否等于2.0。如果存在scale属性,您不能假设设备正在运行iOS4+,因为iPad 3.2也包含该属性。
在运行iOS3.2的iPad上,比例将在1倍模式下返回1.0,在2倍模式下返回2.0--即使我们知道该设备不包含Retina显示屏。Apple在iPad的iOS4.2中更改了此行为:它在1倍和2倍模式下都返回1.0。您可以在模拟器中自行测试。
我会测试主屏幕上是否存在iOS4.x而不存在于iOS3.2的-displayLinkWithTarget: selector:方法,然后检查屏幕的比例。
if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
    ([UIScreen mainScreen].scale == 2.0)) {
  // Retina display
} else {
  // non-Retina display
}

你说“在iOS4.2为iPad中,苹果改变了这种行为”,这意味着在iOS4.1中,你上面的代码会针对以2倍模式运行的iPhone应用在iPad上返回“是Retina”。 我错了吗? - makdad
9
iPad从来没有发布过4.1版本,只有3.2和4.2两个版本。 - Jonny
11
这个调用有点昂贵,所以我会在应用启动时初始化一个BOOL,并在应用中使用它。 - n13
我更喜欢使用[UIDevice currentDevice].systemVersion]来检查版本。在这种情况下,代码如下:NSString *currentSystemVersion = [[UIDevice currentDevice] systemVersion]; return [currentSystemVersion compare:version options:NSNumericSearch]; - Sandy Chapman
在Xcode 4中,非视网膜iPad(iOS 7.1)仿真器似乎无法正常工作...很奇怪。 - Isaac Paul
为什么要检查 @selector(displayLinkWithTarget:selector:) 而不是 @selector(scale) - Alexander Farber

81

@sickp的答案是正确的。为了使事情更容易,将以下行添加到您的Shared.pch文件中:

#import <UIKit/UIKit.h>

#define IS_RETINA ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale >= 2.0))

然后在任何文件中,您只需要执行以下操作:

if(IS_RETINA)
{
   // etc..
}

这在模拟器上不起作用。是因为 respondsToSelector 吗?模拟器不响应选择器吗? - arniotaki
2
太好了!但是如果你想考虑到iPhone 6 Plus的话,你应该检查比例是否大于等于2.0。 - Ivan Carosati

19
+(BOOL)iPhoneRetina{
    return ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0))?1:0;
}

23
为什么要使用 ?1:0?这不是在重复布尔表达式已经计算出来的结果吗? - d11wtq

11

这里有一个方便的 Swift 扩展:

适用于 Swift v5 的更新:

extension UIScreen {

    public var isRetina: Bool {
        guard let scale = screenScale else {
            return false
        }
        return scale >= 2.0
    }

    public var isRetinaHD: Bool {
        guard let scale = screenScale else {
            return false
        }
        return scale >= 3.0
    }

    private var screenScale: CGFloat? {
        guard UIScreen.main.responds(to: #selector(getter: scale)) else {
            return nil
        }
        return UIScreen.main.scale
    }
}

使用方法:

if UIScreen.main.isRetina {
    // Your code
}

Original:

extension UIScreen { 
public func isRetina() -> Bool {
    return screenScale() >= 2.0
}

public func isRetinaHD() -> Bool {
    return screenScale() >= 3.0
}

private func screenScale() -> CGFloat? {
    if UIScreen.mainScreen().respondsToSelector(Selector("scale")) {
        return UIScreen.mainScreen().scale
    }
    return nil
    }
}

使用方法:

if UIScreen.mainScreen().isRetina() {
 // your code
        }

更新为Swift 5的代码应该不是检查iscreenScale是否>=2.0而是>=3.0的Retina HD吗?编辑:我已经更新了它... - C0D3

6

这种特定用法已经被SSToolKit中的SAMCategories取代:[[UIScreen mainScreen] sam_isRetina] - tempire

6

This snippet...

    int d = 0; // standard display
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] && [[UIScreen mainScreen] scale] == 2.0) {
    d = 1; // is retina display
}

if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    d += 2;
}

将返回以下值: 0 表示标准分辨率的 iPhone/iPod touch, 1 表示 retina 版本的 iPhone, 2 表示标准分辨率的 iPad, 3 表示 retina 版本的 iPad。


6

比较浮点数是否相等总是让人感到有些不可靠。我更喜欢采用以下方式之一:

[UIScreen mainScreen].scale > 1.0;

或者

[UIScreen mainScreen].scale < 2.0;

5
将两个浮点数进行相等比较“感觉不可靠”,因为计算后它们可能会略微与整数值不同。但是,在这种情况下,使用 < 或 > 进行比较同样不可靠。然而,在此情况下,刻度绝对没有不是精确1.0或2.0,因为它是硬件定义的。 - fishinear
正如@fishinear所建议的那样,最好使用类似isRetina = [UIScreen mainScreen].scale > 1.95的东西。这也将具有对@4x出现时的弹性的好处 :) - Danyal Aytekin
我强烈不同意。在不需要的情况下这样做会使代码变得难以阅读。关于未来可扩展性的观点可能是有其合理性的,但我怀疑我们很快(如果有的话)就会拥有@4x屏幕。 - Ricardo Sanchez-Saez
错误。仅仅因为它是“硬件定义”的,并不意味着你可以避免比较浮点数的问题。(它只是像其他任何浮点数一样。)与任何浮点数一样,通常情况下你不能使用==,必须使用>或<进行比较。那么对于确定性,大于1.5怎么样? - Fattie

2

以下是与上述答案相对应的Swift版本,使用>= 2.0比例,因此包括iPhone 6+和其他未来具有高于Retina比例的设备:

 if UIScreen.mainScreen().respondsToSelector(Selector("scale")) && UIScreen.mainScreen().scale >= 2.0 {
    // code executed only on Retina device
}

2
这是对Matt MC上面答案的一种变化。仅仅是在UIScreen类别上进行的。
#import "UIScreen+Util.h"

@implementation UIScreen (Util)

+ (BOOL) isRetinaDisplay {
    static BOOL retina = NO;
    static BOOL alreadyChecked = NO;
    if (!alreadyChecked) {
        UIScreen *mainScreen = self.mainScreen;
        if (mainScreen) {
            retina = mainScreen.scale > 1.0;
            alreadyChecked = YES;
        }
    }
    return retina;
}

@end

1
我确实怀疑对alreadyChecked进行缓存是多余的,但没关系。 - Dan Rosenstark
@NikolayShubenkov 这就是为什么我先设置了 alreadyChecked。在最坏的情况下,你可能需要再运行代码一次或两次来检查。 - Dan Rosenstark
我的意思是,当一个进程尝试读取alreadyChecked的值时,另一个进程正在读取该值,应用程序可能会崩溃。我会添加这行代码:@synchronyze(alreadyChecked){alreadyChecked = YES} - Nikolay Shubenkov

1

只是为了结合@sickp的答案和@n13的以下评论,我将其制作成了一个UIScreen类别,这似乎很好地工作。检查是在第一次调用时完成的,然后保存以供以后调用。

@interface UIScreen (RetinaCheck)
+ (BOOL)retinaScreen;
@end

static BOOL isRetinaScreen = NO;
static BOOL didRetinaCheck = NO;

@implementation UIScreen (RetinaCheck)
+ (BOOL)retinaScreen
{
    if (!didRetinaCheck) {
        isRetinaScreen = ([[self mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
                          ([self mainScreen].scale == 2.0));
        didRetinaCheck = YES;
    }
    return isRetinaScreen;
}
@end

可能对某人有用。

感谢提供缓存代码。我的唯一建议是将其命名为(Util)而不是(RetinaCheck)...也许这不太清晰,但它适用于其他用途。此外,我会将该方法命名为isRetinaDisplay或以is开头的名称,但也许我从未理解Obj-C的指南。另外,我喜欢> 1.0,但谁知道未来什么才是有意义的。 - Dan Rosenstark

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