iOS:Dark Mode-暗黑模式調(diào)研

背景

iOS 13蘋(píng)果公司推出了暗黑模式脸秽,APP默認(rèn)支持猩谊,用戶可以通過(guò)在設(shè)置-顯示與亮度-外觀欄中選擇深色來(lái)打開(kāi)暗黑模式揪罕,但是讹堤,如果開(kāi)發(fā)工程師不進(jìn)行適配吆鹤,應(yīng)用內(nèi)可能會(huì)出現(xiàn)某些視圖的顏色變成黑色,影響顯示效果洲守。

要防止這種情況可以給控制器或者視圖設(shè)置overrideUserInterfaceStyle屬性為UIUserInterfaceStyleLight或者UIUserInterfaceStyleDark疑务,這樣當(dāng)前視圖和它的所有子視圖都會(huì)固定為Dark或者Light模式。也可以在info.plist中加入UIUserInterfaceStyle鍵梗醇,給定Light值知允,使整個(gè)應(yīng)用忽略暗黑模式。

蘋(píng)果公司在News And Updates這樣說(shuō):

If you need more time to make your apps look fantastic in Dark Mode, or if Dark Mode is not suited for your app, you can learn how to opt out.如果你需要更多的時(shí)間讓你的APP在暗黑模式下更加出色叙谨,或者暗黑模式不適合你的APP温鸽,你可以學(xué)習(xí)如何退出。

同時(shí)唉俗,適配暗黑模式是強(qiáng)烈建議的嗤朴,僅在適配暗黑模式的過(guò)程中配椭,使用UIUserInterfaceStyle鍵暫時(shí)退出:

Choosing a Specific Interface Style for Your iOS App:Supporting Dark Mode is strongly encouraged. Use the UIUserInterfaceStyle key to opt out only temporarily while you work on improvements to your app's Dark Mode support.

原理

蘋(píng)果公司使用UITraitCollection對(duì)象記錄界面環(huán)境特征,里面包含Size Class雹姊,Layout Direction股缸,User Interface Style信息(Dark或者Light)。每個(gè)UIView吱雏,UIViewController和UIPresentationController對(duì)象都持有這個(gè)對(duì)象敦姻。子視圖被添加到父視圖的時(shí)候,子視圖會(huì)繼承父視圖的UITraitCollection歧杏,UITraitCollection信息就從UIScreen一直傳遞到當(dāng)前顯示的UIView:UIScreen->UIWindow->UIPresentationViewController->UIViewController→UIView镰惦。

用戶更改了系統(tǒng)外觀后,系統(tǒng)通過(guò)調(diào)用以下方法重新渲染視圖犬绒,完成系統(tǒng)外觀的切換:

UIView:
traitCollectionDidChange(_:)
layoutSubviews()
draw(_:)
updateConstraints()
tintColorDidChange()

UIViewController:
traitCollectionDidChange(_:)
updateViewConstraints()
viewWillLayoutSubviews()
viewDidLayoutSubviews()

UIPresentationController:
traitCollectionDidChange(_:)
containerViewWillLayoutSubviews()
containerViewDidLayoutSubviews()

在這些方法調(diào)用前旺入,系統(tǒng)會(huì)更新UITraitCollection對(duì)象,所以要在這些方法中加入Dark模式和Light模式有區(qū)別的代碼凯力,如Dark模式下要在圖片上加一層遮罩茵瘾,Light則要隱藏。如果寫(xiě)在別的地方咐鹤,如在初始化方法或者viewDidLoad中拗秘,會(huì)造成模式切換后,遮罩還在祈惶,或者一直不顯示雕旨。

適配

在適配實(shí)踐中會(huì)總結(jié)出更好的實(shí)現(xiàn)方式,或者發(fā)現(xiàn)很多細(xì)節(jié)需要處理捧请,這些都會(huì)影響開(kāi)發(fā)時(shí)間凡涩。所以調(diào)研時(shí)編寫(xiě)Demo并根據(jù)實(shí)際項(xiàng)目調(diào)試效果是很有必要的。

顏色適配

顏色適配只要將UIColor對(duì)象改成動(dòng)態(tài)顏色對(duì)象即可血久。動(dòng)態(tài)顏色對(duì)象在不同的外觀下突照,有不同的顏色值。它也是UIColor對(duì)象氧吐,但是創(chuàng)建的方式不一樣。UIKit會(huì)根據(jù)UITraitCollection信息解析出對(duì)應(yīng)外觀的顏色值末盔。具體使用如下:

if (@available(iOS 13.0, *)) {
    label.textColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
        if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            return [[UIColor secondarySystemBackgroundColor] resolvedColorWithTraitCollection:traitCollection];
        } else {
            return lightColor;
        }
    }];
} else {
    label.textColor = lightColor;
};

colorWithDynamicProvider是創(chuàng)建動(dòng)態(tài)顏色的方法筑舅。resolvedColorWithTraitCollection是把動(dòng)態(tài)顏色解析成固定顏色的方法,在創(chuàng)建動(dòng)態(tài)顏色的block中不能返回動(dòng)態(tài)顏色陨舱,這里在Dark模式下使用了系統(tǒng)的secondarySystemBackgroundColor動(dòng)態(tài)顏色翠拣,所以返回時(shí)做了解析。

動(dòng)態(tài)顏色也可以通過(guò)Xcode創(chuàng)建游盲,步驟如下:

image.png

使用的時(shí)候用指定方法獲取误墓,如下:

<pre>if (@available(iOS 11.0, *)) {
    label.textColor = [UIColor colorNamed:@"testColor"];
} else {
    label.textColor = UIColor.redColor;
}</pre>

colorNamed方法只支持iOS11以上版本蛮粮。

看起來(lái)使用很麻煩。具體項(xiàng)目中運(yùn)用可以封裝一下谜慌。封裝代碼案例如下:

#define MJCOLOR [MJDynamicColor shareInstance]
//所有動(dòng)態(tài)顏色獲取的地方,適配暗黑模式
@interface MJDynamicColor : NSObject
+ (instancetype)shareInstance;
//背景色
/// 一級(jí)背景色然想,如UIViewController的View的背景色,一般是四周都能接觸到屏幕的視圖的背景色
@property (nonatomic, strong) UIColor *mj_backgroundColor;
/// 二級(jí)背景色欣范,如UITableViewCell的背景色
@property (nonatomic, strong) UIColor *mj_secondaryBackgroundColor;
/// 三級(jí)背景色变泄,如UITableViewCell中button的背景色,一般是最上層的視圖的背景色
@property (nonatomic, strong) UIColor *mj_tertiaryBackgroundColor;
// UILabel的文字的顏色
/// 類(lèi)似一級(jí)標(biāo)題
@property (nonatomic, strong) UIColor *mj_labelColor;
/// 類(lèi)似二級(jí)標(biāo)題
@property (nonatomic, strong) UIColor *mj_secondaryLabelColor;
/// 類(lèi)似三級(jí)標(biāo)題
@property (nonatomic, strong) UIColor *mj_tertiaryLabelColor;
/// 類(lèi)似四級(jí)標(biāo)題
@property (nonatomic, strong) UIColor *mj_quaternaryLabelColor;
@end

@implementation MJDynamicColor
...此部分代碼省略恼琼,都是類(lèi)似下面代碼重寫(xiě)get方法妨蛹,使用懶加載

- (UIColor *)mj_labelColor {
    if (!_mj_labelColor) {
        UIColor *lightColor = [UIColor mjl_colorFromHexString:@"0x666666" alpha:1.0];
        if (@available(iOS 13.0, *)) {
            _mj_labelColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
                if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                    return [[UIColor labelColor] resolvedColorWithTraitCollection:traitCollection];
                } else {
                    return lightColor;
                }
            }];
        } else {
            _mj_labelColor = lightColor;
        };
    }
    return _mj_labelColor;
}
@end

這樣有兩個(gè)好處:一是懶加載使得性能提高了;二是多個(gè)地方使用相同的顏色時(shí)更方便統(tǒng)一修改晴竞。

使用起來(lái)如下:

label.textColor = MJCOLOR.mj_labelColor;

以上使用方式都是創(chuàng)建動(dòng)態(tài)顏色蛙卤,也就是自定義的動(dòng)態(tài)顏色,蘋(píng)果的API也提供了官方的動(dòng)態(tài)顏色噩死,也稱(chēng)為語(yǔ)義顏色颤难,直接使用就可以,在UIInterface.h文件中可以看到甜滨。

圖片適配

圖片適配和顏色適配類(lèi)似乐严,也有動(dòng)態(tài)圖片的概念,通過(guò)XCode創(chuàng)建衣摩,在.xcassets文件中把圖片改成動(dòng)態(tài)圖片就行:

image.png

使用處的代碼不用修改昂验,還是通過(guò)imageNamed方法獲取。這個(gè)是iOS13之前的方法艾扮,所以不用判斷系統(tǒng)版本

[self.leftCloseButton setImage:[UIImage imageNamed:@"feeds_back_white"]

在夜間模式下如果重新使用一張圖片既琴,會(huì)使得圖片資源大小翻倍,所以一般都是加一層遮罩泡嘴,特定情況下才使用新圖片甫恩。這種情況有種偷懶的方法:

#import "UIImageView+NightMask.h"

static const char *MJUIImageViewNightMaskKey = "MJUIImageViewNightMaskKey";

@implementation UIImageView (NightMask)

- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];
    if (@available(iOS 13.0, *)) {
        if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
            if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                self.mj_nightMask.hidden = false;
            } else {
                self.mj_nightMask.hidden = true;
            }
        }
    } else {
        // Fallback on earlier versions
    }
}

- (UIView *)mj_nightMask {
    UIView *obj = objc_getAssociatedObject(self, MJUIImageViewNightMaskKey);
    if (!obj) {
        UIView *view = [[UIView alloc] init];
        view.backgroundColor = [UIColor mjl_colorFromHexString:@"0x000000" alpha:1.0];
        view.alpha = 0.3;
        [self addSubview:view];
        view.frame = self.bounds;
        objc_setAssociatedObject(self, MJUIImageViewNightMaskKey, view, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return obj;
}

- (void)didMoveToWindow:(UIWindow *)newWindow {
    if (@available(iOS 13.0, *)) {
        if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            self.mj_nightMask.hidden = false;
        } else {
            self.mj_nightMask.hidden = true;
        }
    } else {
        // Fallback on earlier versions
    }
}

@end

給UIImageView添加一個(gè)分類(lèi),重寫(xiě)traitCollectionDidChange方法酌予,這個(gè)方法在traitCollection更改時(shí)會(huì)調(diào)用磺箕,這時(shí)候加一個(gè)遮罩就可以了。重寫(xiě)了didMoveToWindow方法的原因是抛虫,在Dark模式下啟動(dòng)APP松靡,不會(huì)顯示Dark模式(夜間模式)的外觀。因?yàn)?code>traitCollectionDidChange沒(méi)有調(diào)用建椰,這個(gè)方法在traitCollection更改的時(shí)候才會(huì)調(diào)用雕欺。

總結(jié):

編寫(xiě)Demo時(shí)發(fā)現(xiàn)的很費(fèi)時(shí)間的點(diǎn),也發(fā)現(xiàn)給圖片或者顏色統(tǒng)一做處理行不通,夜間模式下的每個(gè)頁(yè)面都需要UI重新設(shè)計(jì)屠列,開(kāi)發(fā)也需要重新聯(lián)調(diào)每個(gè)頁(yè)面啦逆,需要的時(shí)間非常漫長(zhǎng)。

  1. 在顏色適配中笛洛,每個(gè)夜間(Dark)模式下給的顏色都要UI重新設(shè)計(jì)夏志,并和開(kāi)發(fā)聯(lián)調(diào),因?yàn)橐归g模式下撞蜂,不能隨便給一個(gè)對(duì)應(yīng)顏色盲镶,使用蘋(píng)果官方提供的動(dòng)態(tài)顏色效果也差(相當(dāng)于開(kāi)發(fā)人員自己來(lái)設(shè)計(jì)UI,并反復(fù)調(diào)試效果)蝌诡。所以需要給APP所有頁(yè)面重新設(shè)計(jì)一套夜間模式下的UI溉贿。

  2. 圖片適配中,單一處理行不通浦旱,也需要每個(gè)頁(yè)面單獨(dú)過(guò)UI如:

    1. 統(tǒng)一添加遮罩:本身就是起遮罩作用的UIImageView宇色。
    2. 統(tǒng)一添加遮罩:很多圖片的外邊緣部分是透明的,加遮罩后外邊緣不透明了颁湖,顯示出邊緣部分了宣蠕。
    3. 統(tǒng)一添加遮罩:有些圖片會(huì)動(dòng)態(tài)修改大小,但是遮罩不會(huì)跟隨著變動(dòng)甥捺。
    4. 給定新圖片 :新圖片也需要UI重新設(shè)計(jì)的時(shí)間抢蚀,因?yàn)橐WC和頁(yè)面其它部分協(xié)調(diào),直接給定一張單一方式處理的圖片镰禾,如只是改了下圖片的亮度皿曲,很可能跟頁(yè)面不協(xié)調(diào)。

折中方案:

  1. 只修改背景色吴侦,圖片和文字在用戶能正常使用的情況下都不做處理屋休,我看微信在夜間模式下的效果基本就是這樣。
  2. 整個(gè)APP不支持暗黑模式备韧,因?yàn)樘O(píng)果官方并沒(méi)有在APP Store Review Guidelines中規(guī)定某些類(lèi)型APP必須兼容暗黑模式劫樟,某些不需要兼容,而是沒(méi)有提到相關(guān)內(nèi)容织堂。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叠艳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子易阳,更是在濱河造成了極大的恐慌虑绵,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闽烙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)黑竞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)捕发,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人很魂,你說(shuō)我怎么就攤上這事扎酷。” “怎么了遏匆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵法挨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我幅聘,道長(zhǎng)凡纳,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任帝蒿,我火速辦了婚禮荐糜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘葛超。我一直安慰自己暴氏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布绣张。 她就那樣靜靜地躺著答渔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侥涵。 梳的紋絲不亂的頭發(fā)上沼撕,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音独令,去河邊找鬼端朵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛燃箭,可吹牛的內(nèi)容都是我干的冲呢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼招狸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敬拓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起裙戏,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乘凸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后累榜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體营勤,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灵嫌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葛作。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寿羞。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赂蠢,靈堂內(nèi)的尸體忽然破棺而出绪穆,到底是詐尸還是另有隱情,我是刑警寧澤虱岂,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布玖院,位于F島的核電站,受9級(jí)特大地震影響第岖,放射性物質(zhì)發(fā)生泄漏难菌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一绍傲、第九天 我趴在偏房一處隱蔽的房頂上張望扔傅。 院中可真熱鬧,春花似錦烫饼、人聲如沸猎塞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荠耽。三九已至,卻和暖如春比藻,著一層夾襖步出監(jiān)牢的瞬間铝量,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工银亲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慢叨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓务蝠,卻偏偏與公主長(zhǎng)得像拍谐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馏段,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 最近公司業(yè)務(wù)需求要更換APP主題轩拨。最開(kāi)始是一個(gè)地方一個(gè)地方去改,而且項(xiàng)目中很多老代碼是用xib寫(xiě)的院喜,習(xí)慣純代碼編程...
    抓魚(yú)貓L閱讀 6,143評(píng)論 0 10
  • 目錄 1.適配暗黑模式(Dark Mode)1.1顏色適配* 系統(tǒng)動(dòng)態(tài)顏色** 自定義動(dòng)態(tài)UIColor(代碼自定...
    冰點(diǎn)雨閱讀 3,053評(píng)論 1 11
  • iOS 13終于引來(lái)了暗黑模式亡蓉。 每當(dāng)新特性的到來(lái),iOS開(kāi)發(fā)者們既緊張又有點(diǎn)小興奮喷舀,懷揣著被虐的心態(tài)砍濒,讓我們來(lái)看...
    koin447閱讀 63,651評(píng)論 16 106
  • 掙扎中的拖延著:成為戰(zhàn)敗者怎么辦 爭(zhēng)奪控制權(quán)的較量 對(duì)我們每個(gè)人來(lái)說(shuō)梯影,對(duì)自己的生活具有一定的掌控感是十分重要的一件...
    Dl_毛良偉閱讀 194評(píng)論 0 1
  • 嬌蘭花開(kāi)一二朵 吐蕊繞蝶三四月 鐘情已有五六載 相思七八里 九十是歸期
    蘭妤妤閱讀 203評(píng)論 0 1