屏幕適配

基礎(chǔ)知識

  • 屏幕尺寸
    手機屏幕對角線的長度 1英寸(inch)=2.54厘米(cm)

  • 分辨率
    在我們手機上呈現(xiàn)的一條線,一個面,一張圖像都是由最小的單位像素來表示的,也可以簡單理解為是由一個個小方塊組成的。
    例:當(dāng)5.2英寸的手機分辨率1920px*1080px
    即手機在豎向的高度上有1920個像素塊,在橫向的寬度上有1080個像。

  • 像素密度
    屏幕像素密度,即每英寸屏幕所擁有的像素數(shù),英文簡稱PPI
    每英寸不是每平方英寸的簡稱,
    這個英寸跟之前手機屏幕的尺寸一樣,也是對角線的長度
    即在一個對角線長度為1英寸的正方形內(nèi)所擁有的像素數(shù)。
    例: 5.2寸手機分辨率1920 *1080 px 密度約為424
    (1920px就是公式里的縱向,1080px就是公式里的橫向)

  • 像素沒有固定的物理性大小
    蘋果6手機 ,5英寸, 1920 * 1080 px, 469 ppi
    榮耀7手機, 5.2英寸,1920 *1080px, 424 ppi
    分辨率相同,蘋果手機的屏幕尺寸比華為榮耀7小了0.2英寸,
    但蘋果(PPI)卻比華為榮耀7高了45個PPI。

    一個像素其實就是一個色彩塊,
    同樣的一英寸,蘋果手機展示469個色彩,華為展示424個色彩,
    蘋果手機的顯示效果當(dāng)然就更好了。

  • 電腦調(diào)整分辨率,是弄啥呢
    在同一個設(shè)備上,它的像素個數(shù)是固定的,廠商在出廠時就設(shè)置好了。只有不同的設(shè)備之間,才有像素大小的區(qū)別。既然在同一個設(shè)備上,像素點數(shù)早就設(shè)定好了,那電腦上可以調(diào)整分辨率是怎么回事?

    系統(tǒng)給你推薦的是1366px*768px的分辨率,
    意味著微軟在這塊屏幕上橫向設(shè)置了768個像素,豎向設(shè)置了1366個像素。這個數(shù)字是確定且不會改變的。

    如果把分辨率調(diào)成800 * 600,系統(tǒng)就會分配給你800 * 600個有效像素個數(shù),也就是真實的色彩塊。其他的個數(shù)呢,就由系統(tǒng)自作主張,通過一系列運算給你一個模擬色彩塊,填充成正好1366*768個色彩塊。

導(dǎo)航欄高度

iphone 系列開發(fā)尺寸
手機型號 Physical Device Points (pt) Rendered Pixels(px) Render 屏幕密度
2G 3G 3GS 3.5 320*480 320*480 @2x 326
4 4S 3.5 320*480 640*960 @2x 326
5 5s 5c SE 4 320*568 640*1136 @2x 326
6 6s 7 8 4.7 375*667 750*1334 @2x 326
6+ 6S+ 7+ 8+ 5.5 414*736 1242*2208 @3x 401
X Xs 5.8 375*812 1125*2436 @3x
Xr 6.1 414*896 828*1792 @2x
Xs Max 6.5 414*896 1242*2688 @3x
判斷iPhoneX Type機型的方法匯總

方式一:通過獲取設(shè)備的 device model 來判斷
每一臺 iOS 設(shè)備都有對應(yīng)的硬件編碼/標(biāo)識符,
稱為 device model 或者叫 machine name,
可以通過如下兩種方法來獲取 device model/machine name

#import <sys/sysctl.h>
#import <sys/utsname.h>

// 獲取 device model/machine name 的方法一
+ (NSString *)machineName1 {
    size_t size;
    sysctlbyname("hw.machine", NULL, &size, NULL, 0);
    char *machine = (char *)malloc(size);
    if (machine == NULL) {
        return nil;
    }
    sysctlbyname("hw.machine", machine, &size, NULL, 0);
    NSString *platform = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
    free(machine);
    return platform;
}

// 獲取 device model/machine name 的方法二
+ (NSString *)machineName2 {
    struct utsname systemInfo;
    uname(&systemInfo);
    return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
}

iPhone X 對應(yīng)的 device mode 為 iPhone10,3 和 iPhone10,6
iPhone XS 對應(yīng) iPhone11,2,
iPhone XS Max 對應(yīng) iPhone11,4 和 iPhone11,6,
iPhone XR 對應(yīng) iPhone11,8
參考鏈接

上述兩種獲取 device model 的方法在模擬器中運行得到的值為 i386 或 x86_64,
因此在模擬器中正確獲取模擬器所對應(yīng)的 device model:

// 獲取模擬器所對應(yīng)的 device model
NSString *model = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"];

綜上完整代碼應(yīng)該是

+ (BOOL)isiPhoneX {
    static BOOL isiPhoneX = NO;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
#if TARGET_IPHONE_SIMULATOR
        // 獲取模擬器所對應(yīng)的 device model
        NSString *model = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"];
#else
        // 獲取真機設(shè)備的 device model
        struct utsname systemInfo;
        uname(&systemInfo);
        NSString *model = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
#endif
        // 判斷 device model 是否為 "iPhone10,3" 和 "iPhone10,6" 或者以 "iPhone11," 開頭
        // 如果是,就認(rèn)為是 iPhone X
        isiPhoneX = [model isEqualToString:@"iPhone10,3"] || [model isEqualToString:@"iPhone10,6"] || [model hasPrefix:@"iPhone11,"];
    });
    
    return isiPhoneX;
}

方式二:通過獲取屏幕的寬高來判斷
目前 iPhone X 設(shè)備的屏幕寬高對應(yīng)的開發(fā)尺寸只有兩種,
分別為 375pt * 812pt 和 414pt * 896pt,因此我們可以根據(jù)屏幕的高度來判斷設(shè)備是否為 iPhone X。
但是此時需要考慮設(shè)備處于橫屏或者豎屏的情況

在 UIDevice 中提供了一個 orientation 屬性用于獲取設(shè)備的方向(橫向、豎向、或者水平),一開始我們想著先通過這個屬性判斷設(shè)備處于橫屏或者豎屏,然后分別取其對應(yīng)的屏幕寬度(橫屏下)或者高度(豎屏下)來判斷,但是當(dāng)這個屬性的值為 FaceUp 或者 FaceDown(即設(shè)備放在水平面上),我們是無法知道此時設(shè)備是處于橫屏還是豎屏的。

后面我們想了一個簡便的方法,即獲取屏幕的寬度和高度,取較大一方進行比較是等于 812.0 或 896.0,代碼如下:

+ (BOOL)isiPhoneX {
    // 先判斷當(dāng)前設(shè)備是否為 iPhone 或 iPod touch
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        // 獲取屏幕的寬度和高度,取較大一方判斷是否為 812.0 或 896.0
        CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
        CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
        CGFloat maxLength = screenWidth > screenHeight ? screenWidth : screenHeight;
        if (maxLength == 812.0f || maxLength == 896.0f) {
            return YES;
        }
    }
    return NO;
}

方式三:通過底部安全區(qū)域的高度來判斷
iPhone X 發(fā)布后,為了適配頂部的瀏覽和底部的操作條,蘋果在 iOS 11 上引入安全區(qū)域概念,建議開發(fā)者在安全區(qū)域內(nèi)進行 UI 布局,因此我們可以獲取屏幕 keyWindow 的 safeAreaInsets 值來判斷設(shè)備是否 iPhone X。

//iPhone X 
//豎屏,keyWindow 的 safeAreaInsets 值為:
{top: 44, left: 0, bottom: 34, right: 0}
//橫屏
{top: 0, left: 44, bottom: 21, right: 44}

我們可以比較 safeAreaInsets 的 bottom 是否等于 34.0 或者 21.0 來判斷設(shè)備是否為 iPhone X,
因為其他設(shè)備對應(yīng)的 bottom 橫豎屏下都為 0

+ (BOOL)isiPhoneX {
    if (@available(iOS 11.0, *)) {
        UIWindow *keyWindow = [[[UIApplication sharedApplication] delegate] window];
        // 獲取底部安全區(qū)域高度,iPhone X 豎屏下為 34.0,橫屏下為 21.0,其他類型設(shè)備都為 0
        CGFloat bottomSafeInset = keyWindow.safeAreaInsets.bottom;
        if (bottomSafeInset == 34.0f || bottomSafeInset == 21.0f) {
            return YES;
        }
    }
    return NO;
}

不足:必須在 AppDelegate 的 didFinishLaunchingWithOptions 回調(diào)中等 keyWindow 初始化之后才能正確判斷

方式四:通過是否支持 FaceID 判斷
由于目前只有 iPhone X 設(shè)備支持 FaceID,因此我們也可以通過判斷設(shè)備是否支持 FaceID 來判斷,代碼如下:

+ (BOOL)canUseFaceID {
    if (@available(iOS 11.0, *)) {
        // will fail if user denies `canEvaluatePolicy:error:`
        LAContext *context = [[LAContext alloc] init];
        if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil]) {
            return (context.biometryType == LABiometryTypeFaceID);
        }
    }
    return NO;
}

不足:如果用戶禁用 canEvaluatePolicy:error: 方法的使用將無法正確判斷,而且在也不適用于模擬器中的判斷。
方式五:通過 UIStatusBar 的高度判斷
在 iPhone X 之前,所有 iPhone 設(shè)備的 StatusBar(狀態(tài)欄)高度都為 20pt,而 iPhone X 的為 44pt,因此我們可以通過獲取狀態(tài)欄的高度判斷是否等于 44.0 來檢測設(shè)備是否為 iPhone X,代碼如下:

+ (BOOL)isiPhoneX {
    CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
    if (statusBarFrame.size.height == 44.0f) {
        return YES;
    }
    return NO;
}

不足:該方法只適用于豎屏且顯示狀態(tài)欄的情況下才能正確檢測,
而在橫屏模式下,或者 App 隱藏導(dǎo)航欄時,獲取到的狀態(tài)欄高度都為 0(statusBarFrame 的值為 CGRectZero),就無法判斷了。

系統(tǒng)版本的宏
#define IOS [[[UIDevice currentDevice] systemVersion] floatValue]
#define IOS8 [[[UIDevice currentDevice] systemVersion] floatValue] >= 8 ? YES : NO
#define IOS9 [[[UIDevice currentDevice] systemVersion] floatValue] >= 9 ? YES : NO
#define IOS10 [[[UIDevice currentDevice] systemVersion] floatValue] >= 10 ? YES : NO
#define IOS11 [[[UIDevice currentDevice] systemVersion] floatValue] >= 11 ? YES

其它宏
// debug下打印日志, release下不執(zhí)行NSLog代碼
#ifdef DEBUG
#define NSLog(...) printf("myAppInfo %s\n %s\n",__func__, [[NSString stringWithFormat:__VA_ARGS__]UTF8String]);
#else
#define NSLog(format, ...)
#endif

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容