226

Does iOS SDK provides an easy way to check if the currentDevice has an high-resolution display (retina) ?

The best way I've found to do it now is :

    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] == YES && [[UIScreen mainScreen] scale] == 2.00) {
         // RETINA DISPLAY
    }
TheNeil
  • 3,321
  • 2
  • 27
  • 52
Pierre Valade
  • 3,388
  • 3
  • 24
  • 29
  • Out of curiosity - what are you doing when you detect the display other than showing larger versions of your art work? – Michael Behan Aug 17 '10 at 15:35
  • 4
    possible duplicate of [How to differentiate between iphone4 and iphone 3](http://stackoverflow.com/questions/3294100/how-to-differentiate-between-iphone4-and-iphone-3) – Kendall Helmstetter Gelner Aug 17 '10 at 17:09
  • @mbehan: I have a TTImageView (see Three20 framework) and I want to give an high-resolution url of the image. – Pierre Valade Aug 18 '10 at 10:05
  • 1
    This question is also useful to me because I have downloaded images that present as UI available in sizes for all 4 display sizes & only want to have users download the appropriate one. – Pedro Aug 22 '12 at 16:54
  • @mbehan: in my case I wanted custom cell separators that are 1px on both retina & non-retina screens (like the native separators). Setting the thickness to 1px renders at 2px on retina displays (obviously). – user3099609 Sep 19 '14 at 12:08
  • Don't forget that with the iPhone6 and 6 plus you need to test for scale '>= 2.0', not just '== 2.0' – James Dec 16 '14 at 21:28

14 Answers14

296

In order to detect the Retina display reliably on all iOS devices, you need to check if the device is running iOS4+ and if the [UIScreen mainScreen].scale property is equal to 2.0. You CANNOT assume a device is running iOS4+ if the scale property exists, as the iPad 3.2 also contains this property.

On an iPad running iOS3.2, scale will return 1.0 in 1x mode, and 2.0 in 2x mode -- even though we know that device does not contain a Retina display. Apple changed this behavior in iOS4.2 for the iPad: it returns 1.0 in both 1x and 2x modes. You can test this yourself in the simulator.

I test for the -displayLinkWithTarget:selector: method on the main screen which exists in iOS4.x but not iOS3.2, and then check the screen's scale:

if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
    ([UIScreen mainScreen].scale == 2.0)) {
  // Retina display
} else {
  // non-Retina display
}
Trashpanda
  • 14,758
  • 3
  • 29
  • 18
  • You say that "Apple changed this behavior in iOS4.2 for the iPad", implying that in iOS4.1, your code above would return "is Retina" for an iPad running an iPhone app in 2x mode. Am I wrong? – makdad Mar 11 '12 at 12:26
  • 9
    There never was a 4.1 for iPad. Only 3.2, then 4.2. – Jonny Apr 02 '12 at 18:26
  • 11
    This call is a bit expensive so I'd initialize a BOOL with it on app start and use that in the app. – n13 May 20 '12 at 14:22
  • I prefer to check the version using `[UIDevice currentDevice].systemVersion]`. In this case it'd be `NSString *currentSystemVersion = [[UIDevice currentDevice] systemVersion]; return [currentSystemVersion compare:version options:NSNumericSearch];` – Sandy Chapman Dec 09 '13 at 17:15
  • Doesn't seem to work in the simulator for non retina iPad (ios 7.1) in xcode 4... weird. – Isaac Paul Aug 04 '14 at 19:17
  • Why checking for `@selector(displayLinkWithTarget:selector:)` and not for `@selector(scale)`? – Alexander Farber Apr 20 '15 at 14:56
81

@sickp's answer is correct. Just to make things easier, add this line into your Shared.pch file:

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

Then in any file you can just do:

if(IS_RETINA)
{
   // etc..
}
Mick Byrne
  • 14,394
  • 16
  • 76
  • 91
19
+(BOOL)iPhoneRetina{
    return ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0))?1:0;
}
Mani
  • 1,841
  • 15
  • 29
  • 23
    Why the `?1:0` ? Isn't that just reiterating what has already been calculated in the boolean part of the expression? – d11wtq Feb 04 '13 at 04:58
11

Here is a handy swift extension:

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

Usage:

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

Usage:

if UIScreen.mainScreen().isRetina() {
 // your code
        }
C0D3
  • 6,440
  • 8
  • 43
  • 67
primulaveris
  • 966
  • 14
  • 20
  • Shouldn't the code that's update to work for Swift 5 isRetinaHD check if iscreenScale is >= 3.0 not 2.0 ??? Edit: I updated it... – C0D3 Mar 12 '20 at 14:07
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;
}

Will return... 0 for standard resolution iPhone/iPod touch, 1 for retina iPhone, 2 for standard resolution iPad, 3 for retina iPad.

Pedro
  • 878
  • 1
  • 12
  • 29
6

SSToolkit has a method that does this:

http://sstoolk.it/documentation/Categories/UIScreen(SSToolkitAdditions).html

It is used in the following way:

[[UIScreen mainScreen] isRetinaDisplay];
Jorge Perez
  • 1,606
  • 1
  • 13
  • 4
  • This particular usage has been superseded by SAMCategories within SSToolKit: [[UIScreen mainScreen] sam_isRetina] – Tempire Mar 26 '14 at 06:35
6

It always feels a bit dodgy to compare floating-point values for equality. I prefer going for either

[UIScreen mainScreen].scale > 1.0;

or

[UIScreen mainScreen].scale < 2.0;
skahlert
  • 647
  • 4
  • 11
  • 5
    Comparing two floating-point values for equality "feels dodgy", because they can differ slightly from integral values after computations. But comparing with < or > is just as dodgy in those situations. In this case, however, there is no chance at all that scale is not exactly 1.0 or 2.0, as it is hardware defined. – fishinear Mar 22 '13 at 17:19
  • As @fishinear suggests, better to use something like `isRetina = [UIScreen mainScreen].scale > 1.95`. This will also have the benefit of being resilient to when @4x comes along :) – Danyal Aytekin Aug 06 '13 at 15:44
  • I strongly disagree. Doing this when not needed makes code less readable. The point on future-proofness might have its validity, but I doubt we'll have @4x screens any time soon (if at all). – Ricardo Sanchez-Saez Aug 16 '13 at 17:03
  • Wrong. just because it is "hardware defined" does not, in any way, mean you avoid the compare-a-float problem. (It's just a float like any other.) As with any float, in general you can never use ==, you must use a > or < comparison. What about >1.5 for certainty. – Fattie Mar 30 '14 at 17:07
2

This is a riff on Matt MC's answer above. Just a category on 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
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • 1
    I do suspect that the caching of `alreadyChecked` is gratuitous, but it's fine. – Dan Rosenstark Nov 28 '13 at 20:55
  • @NikolayShubenkov that's why I set alreadyChecked last. In the worst case scenario, you run the code to check an extra time or two. – Dan Rosenstark Apr 23 '14 at 01:06
  • I mean when one process will try to alreadyChecked while other is currently reading this value app may crash. I would add that line: @synchronyze(alreadyChecked){alreadyChecked = YES} – Nikolay Shubenkov Apr 25 '14 at 06:51
2

Swift version of the answers above, with >= 2.0 scale so it includes iPhone 6+ and other future devices with higher-than-Retina scale:

 if UIScreen.mainScreen().respondsToSelector(Selector("scale")) && UIScreen.mainScreen().scale >= 2.0 {
    // code executed only on Retina device
}
cdf1982
  • 764
  • 1
  • 18
  • 34
1

Just to combine the answer from @sickp and the following comment from @n13 I made this into a UIScreen category which seems to work nicely. The check is done the first time you call it and then saved for later calls.

@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

Might be useful to someone.

Matt Mc
  • 8,882
  • 6
  • 53
  • 89
  • Thanks for the caching code. My only suggestion is to make this `(Util)` instead of `(RetinaCheck)`... perhaps it's less clear, but it lends itself to other uses. Also I would name the method `isRetinaDisplay` or something that starts with `is`, but maybe I never did understand the guidelines for Obj-C. Also, I'm a fan of `> 1.0` but who knows what will make sense moving forward. – Dan Rosenstark Nov 13 '13 at 20:28
1
// .h
UIKIT_EXTERN bool isRetinaDisplay();

// .m
bool isRetinaDisplay()
{
    static bool flag;
#ifdef __BLOCKS__
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
        {
            flag = [[UIScreen mainScreen] scale] > 1.0;
        }
        else
        {
            flag = false;
        }
    });
#else
    static bool onceToken;
    if(onceToken == false)
    {
        onceToken = true;
        if([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
        {
            flag = [[UIScreen mainScreen] scale] > 1.0;
        }
        else
        {
            flag = false;
        }
    }
#endif
    return flag;
}
Roman Solodyashkin
  • 799
  • 12
  • 17
0

try this

if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
    ([UIScreen mainScreen].scale == 2.0))
{
    // Retina display
    NSLog(@"---------------Retina display");
} else {
    // non-Retina display
    NSLog(@"---------------non-Retina display");
}
KARTHIK RA
  • 469
  • 2
  • 8
0

Modified version of primulaveris's for simplicity of most common use cases. I'm on swift 2.2 but it shouldn't matter.

extension UIScreen {
    static var isRetina: Bool {
        return screenScale >= 2.0
    }

    static var isRetinaHD: Bool {
        return screenScale >= 3.0
    }

    static var screenScale:CGFloat {
        return UIScreen.mainScreen().scale
    }
}

Then simply use them like this

print(UIScreen.isRetina)
print(UIScreen.isRetinaHD)
print(UIScreen.screenScale)
GregP
  • 1,584
  • 17
  • 16
0

This worked for me

if((UIScreen .mainScreen().scale) < 2.0)
{
    NSLog("no retina");
}
else
{
    NSLog("retina");
}