Обнаружение Retina Display


223

Предоставляет ли iOS SDK простой способ проверить, есть ли у currentDevice дисплей с высоким разрешением (сетчатка)?

Лучший способ сделать это сейчас:

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

Из любопытства - что вы делаете, когда обнаруживаете дисплей, кроме показа больших версий ваших художественных работ?
Майкл Бехан

4
Возможный дубликат Как
различить

@mbehan: у меня есть TTImageView (см. Three20 framework), и я хочу дать URL изображения с высоким разрешением.
Пьер Валаде

1
Этот вопрос также полезен для меня, потому что я скачал изображения, которые представлены в виде пользовательского интерфейса, доступного в размерах для всех 4 размеров дисплея, и хочу, чтобы пользователи загружали только соответствующий.
Педро

@mbehan: в моем случае я хотел использовать собственные разделители ячеек размером 1 пиксель как на сетчатке, так и на сетчатке (как собственные разделители). Установка толщины на 1px визуализирует на 2px на дисплеях сетчатки (очевидно).
user3099609

Ответы:


295

Чтобы надежно обнаружить дисплей Retina на всех устройствах iOS, необходимо проверить, работает ли устройство под управлением iOS4 + и [UIScreen mainScreen].scaleравно ли это свойство значению 2.0. Вы НЕ МОЖЕТЕ предположить, что устройство работает под управлением iOS4 +, если scaleсвойство существует, поскольку iPad 3.2 также содержит это свойство.

На iPad под управлением iOS3.2 масштаб вернется к 1,0 в режиме 1x и к 2,0 в режиме 2x - хотя мы знаем, что устройство не имеет дисплея Retina. Apple изменила это поведение в iOS4.2 для iPad: она возвращает 1.0 в режимах 1x и 2x. Вы можете проверить это самостоятельно в симуляторе.

Я проверяю -displayLinkWithTarget:selector:метод на главном экране, который существует в iOS4.x, но не iOS3.2, а затем проверяю масштаб экрана:

if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)] &&
    ([UIScreen mainScreen].scale == 2.0)) {
  // Retina display
} else {
  // non-Retina display
}

Вы говорите, что «Apple изменила это поведение в iOS4.2 для iPad», подразумевая, что в iOS4.1 ваш приведенный выше код вернул бы «is Retina» для iPad, работающего в приложении iPhone в режиме 2x. Я ошибся?
Макдад

9
Там никогда не было 4.1 для iPad. Только 3,2, затем 4,2.
Джонни

11
Этот вызов немного дорог, поэтому я бы инициализировал BOOL вместе с ним при запуске приложения и использовал бы его в приложении.
n13

Я предпочитаю проверить версию с помощью [UIDevice currentDevice].systemVersion]. В этом случае это будет NSString *currentSystemVersion = [[UIDevice currentDevice] systemVersion]; return [currentSystemVersion compare:version options:NSNumericSearch];
Сэнди Чепмен

Кажется, не работает в симуляторе для iPad не сетчатки (IOS 7.1) в Xcode 4 ... странно.
Исаак Павел

81

Ответ @ sickp правильный. Просто, чтобы упростить задачу, добавьте эту строку в файл Shared.pch:

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

Тогда в любом файле вы можете просто сделать:

if(IS_RETINA)
{
   // etc..
}

Это не работает на симуляторе. Это из-за отклика ToSelector? Симулятор не отвечает на селектор?
Арниотаки

2
Большой! Однако, если вы хотите принять во внимание iPhone 6 Plus, вы должны проверить масштаб> = 2.0.
Иван Каросати

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

23
Почему то ?1:0? Разве это не просто повторение того, что уже было вычислено в логической части выражения?
d11wtq

9

Вот удобное быстрое расширение:

Обновление для 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
}

Оригинал:

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, isRetinaHD проверить, iscreenScale> = 3.0, а не 2.0 ??? Изменить: я обновил его ...
C0D3

6

Этот фрагмент ...

    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 для iPhone с сетчаткой, 2 для iPad со стандартным разрешением, 3 для iPad с сетчаткой.



5

Всегда немного глупо сравнивать значения с плавающей точкой на равенство. Я предпочитаю идти на любой

[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 в ближайшее время (если вообще будут).
Рикардо Санчес-Саез

Неправильно. просто потому, что оно «определено аппаратно», никоим образом не означает, что вы избежите проблемы сравнения с плавающей точкой. (Это просто число с плавающей точкой, как и любое другое.) Как и с любым другим числом с плавающей точкой, обычно вы никогда не можете использовать ==, вы должны использовать> или <сравнение. Как насчет> 1,5 для определенности.
Толстяк

2

Это риф на ответ Мэтта 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является бесплатным, но это нормально.
Дэн Розенстарк

@NikolayShubenkov, поэтому я установил уже проверено в прошлом. В худшем случае вы запускаете код для проверки дополнительного времени или двух.
Дэн Розенстарк

Я имею в виду, когда один процесс попытается уже проверить, а другой в данный момент читает это значение, приложение может завершиться сбоем. Я бы добавил эту строку: @synchronyze (уже проверено) {уже проверено = ДА}
Николай Шубенков

2

Быстрая версия ответов выше, со шкалой> = 2.0, поэтому она включает iPhone 6+ и другие будущие устройства со шкалой выше, чем Retina:

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

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но кто знает, в чем будет смысл двигаться вперед.
Дэн Розенстарк

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

Лучшее решение, как я думаю.
Николай Шубенков

0

попробуй это

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

0

Модифицированная версия primulaveris для простоты наиболее распространенных вариантов использования. Я на быстром 2.2, но это не должно иметь значения.

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

Тогда просто используйте их как это

print(UIScreen.isRetina)
print(UIScreen.isRetinaHD)
print(UIScreen.screenScale)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.