Détecter l'affichage de la rétine

223

Le SDK iOS offre-t-il un moyen facile de vérifier si le périphérique actuel dispose d'un écran haute résolution (rétine)?

La meilleure façon de le faire maintenant est:

    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] == YES && [[UIScreen mainScreen] scale] == 2.00) {
         // RETINA DISPLAY
    }
Pierre Valade
la source
Par curiosité - que faites-vous lorsque vous détectez l'affichage autre que de montrer des versions plus grandes de votre œuvre d'art?
Michael Behan
4
doublon possible de Comment différencier l'iphone4 de l'iphone 3
Kendall Helmstetter Gelner
@mbehan: J'ai un TTImageView (voir le cadre Three20) et je veux donner une URL haute résolution de l'image.
Pierre Valade
1
Cette question m'est également utile car j'ai téléchargé des images qui se présentent sous forme d'interface utilisateur disponibles dans les tailles pour les 4 tailles d'affichage et je souhaite uniquement que les utilisateurs téléchargent la bonne.
Pedro
@mbehan: dans mon cas, je voulais des séparateurs de cellules personnalisés qui sont 1px sur les écrans rétine et non-rétine (comme les séparateurs natifs). La définition de l'épaisseur à 1px rend à 2px sur les écrans rétine (évidemment).
user3099609

Réponses:

295

Afin de détecter l'affichage Retina de manière fiable sur tous les appareils iOS, vous devez vérifier si l'appareil exécute iOS4 + et si la [UIScreen mainScreen].scalepropriété est égale à 2.0. Vous NE POUVEZ PAS supposer qu'un appareil exécute iOS4 + si la scalepropriété existe, car l'iPad 3.2 contient également cette propriété.

Sur un iPad exécutant iOS3.2, l'échelle renverra 1,0 en mode 1x et 2,0 en mode 2x - même si nous savons que l'appareil ne contient pas d'écran Retina. Apple a changé ce comportement dans iOS4.2 pour l'iPad: il renvoie 1.0 dans les modes 1x et 2x. Vous pouvez le tester vous-même dans le simulateur.

Je teste la -displayLinkWithTarget:selector:méthode sur l'écran principal qui existe dans iOS4.x mais pas iOS3.2, puis vérifie l'échelle de l'écran:

if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
    ([UIScreen mainScreen].scale == 2.0)) {
  // Retina display
} else {
  // non-Retina display
}
malade
la source
Vous dites que "Apple a changé ce comportement dans iOS4.2 pour l'iPad", ce qui implique que dans iOS4.1, votre code ci-dessus retournerait "est Retina" pour un iPad exécutant une application iPhone en mode 2x. Ai-je tort?
makdad
9
Il n'y a jamais eu de 4.1 pour iPad. Seulement 3.2, puis 4.2.
Jonny
11
Cet appel est un peu cher, donc j'initialiserais un BOOL avec lui au démarrage de l'application et l'utiliserais dans l'application.
n13
Je préfère vérifier la version en utilisant [UIDevice currentDevice].systemVersion]. Dans ce cas, ce serait NSString *currentSystemVersion = [[UIDevice currentDevice] systemVersion]; return [currentSystemVersion compare:version options:NSNumericSearch];
Sandy Chapman
Ne semble pas fonctionner dans le simulateur pour iPad non retina (ios 7.1) dans xcode 4 ... bizarre.
Isaac Paul
81

La réponse de @ malade est correcte. Juste pour faciliter les choses, ajoutez cette ligne dans votre fichier Shared.pch:

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

Ensuite, dans n'importe quel fichier, vous pouvez simplement faire:

if(IS_RETINA)
{
   // etc..
}
Mick Byrne
la source
Cela ne fonctionne pas sur le simulateur. Est-ce à cause de respondsToSelector? Le simulateur ne répond pas au sélecteur?
arniotaki
2
Génial! Cependant, si vous souhaitez prendre en compte l'iPhone 6 Plus, vous devez vérifier l'échelle> = 2.0.
Ivan Carosati
20
+(BOOL)iPhoneRetina{
    return ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0))?1:0;
}
Mani
la source
23
Pourquoi ?1:0? N'est-ce pas simplement réitérer ce qui a déjà été calculé dans la partie booléenne de l'expression?
d11wtq
9

Voici une extension rapide et pratique:

Mise à jour pour 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
        }
primulaveris
la source
Le code qui est mis à jour ne devrait-il pas fonctionner pour Swift 5 isRetinaHD vérifier si iscreenScale est> = 3.0 pas 2.0 ??? Edit: je l'ai mis à jour ...
C0D3
6

Cet extrait ...

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

Sera de retour ... 0 pour iPhone / iPod touch de résolution standard, 1 pour iPhone rétine, 2 pour iPad résolution standard, 3 pour iPad rétine.

Pedro
la source
5

Il est toujours un peu louche de comparer des valeurs à virgule flottante pour l'égalité. Je préfère y aller soit

[UIScreen mainScreen].scale > 1.0;

ou

[UIScreen mainScreen].scale < 2.0;
skahlert
la source
5
La comparaison de deux valeurs à virgule flottante pour l’égalité «semble douteuse», car elles peuvent différer légèrement des valeurs intégrales après les calculs. Mais comparer avec <ou> est tout aussi douteux dans ces situations. Dans ce cas, cependant, il n'y a aucune chance que l'échelle ne soit pas exactement 1.0 ou 2.0, car elle est définie par le matériel.
fishinear
Comme le suggère @fishinear, mieux vaut utiliser quelque chose comme isRetina = [UIScreen mainScreen].scale > 1.95. Cela aura également l'avantage d'être résilient quand @ 4x arrive :)
Danyal Aytekin
Je suis fortement en désaccord. Faire cela lorsqu'il n'est pas nécessaire rend le code moins lisible. Le point sur la pérennité pourrait avoir sa validité, mais je doute que nous aurons bientôt des écrans @ 4x (voire pas du tout).
Ricardo Sanchez-Saez
Faux. le simple fait qu'il soit "défini par le matériel" ne signifie en aucun cas que vous évitez le problème de comparaison-flotteur. (C'est juste un flottant comme les autres.) Comme pour tout flottant, en général, vous ne pouvez jamais utiliser ==, vous devez utiliser une comparaison> ou <. Qu'en est-il de> 1,5 pour plus de certitude.
Fattie
2

Ceci est un riff sur la réponse de Matt MC ci-dessus. Juste une catégorie 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
la source
1
Je soupçonne que la mise en cache de alreadyCheckedest gratuite, mais ça va.
Dan Rosenstark
@NikolayShubenkov c'est pourquoi j'ai mis la dernière fois déjà vérifié. Dans le pire des cas, vous exécutez le code pour vérifier une ou deux heures supplémentaires.
Dan Rosenstark
Je veux dire quand un processus essaiera déjà de vérifier alors que l'autre lit actuellement cette application de valeur peut planter. J'ajouterais cette ligne: @synchronyze (alreadyChecked) {alreadyChecked = YES}
Nikolay Shubenkov
2

Version Swift des réponses ci-dessus, avec une échelle> = 2.0, ce qui inclut l'iPhone 6+ et d'autres futurs appareils avec une échelle supérieure à Retina:

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

Juste pour combiner la réponse de @sickp et le commentaire suivant de @ n13, j'en ai fait une catégorie UIScreen qui semble bien fonctionner. La vérification est effectuée la première fois que vous l'appelez, puis enregistrée pour les appels ultérieurs.

@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

Pourrait être utile à quelqu'un.

Matt Mc
la source
Merci pour le code de mise en cache. Ma seule suggestion est de faire cela (Util)au lieu de (RetinaCheck)... c'est peut-être moins clair, mais cela se prête à d'autres utilisations. Je nommerais également la méthode isRetinaDisplayou quelque chose qui commence par is, mais peut-être que je n'ai jamais compris les directives pour Obj-C. De plus, je suis fan de > 1.0mais qui sait ce qui aura du sens à l'avenir.
Dan Rosenstark
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
la source
La meilleure solution que je pense.
Nikolay Shubenkov
0

essaye ça

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
la source
0

Version modifiée de primulaveris pour la simplicité des cas d'utilisation les plus courants. Je suis sur Swift 2.2 mais cela ne devrait pas avoir d'importance.

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

Ensuite, utilisez-les simplement comme ceci

print(UIScreen.isRetina)
print(UIScreen.isRetinaHD)
print(UIScreen.screenScale)
GregP
la source
0

Cela a fonctionné pour moi

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