
2019年6月4日凌晨,蘋果在開發(fā)者大會上推出了新一代手機操作系統(tǒng) iOS 13,主要更新了照片應(yīng)用、滑動輸入和更多動畫表情,還有就是增加了”深色模式“,優(yōu)化了音量的調(diào)節(jié)方式。
深色模式”終于來了“。
在所有關(guān)于 iOS13 的更新項目里,“深色模式”是網(wǎng)友討論最多的。該模式可以根據(jù)日出日落時間自動開啟,開啟后,不只有壁紙,所有的系統(tǒng)元素都會變成暗色,起到在夜里降低屏幕亮度、保護用戶眼睛的作用。

實際上,蘋果用戶很早就開始呼吁 iOS 加入這個深色模式。如今 iOS13 正式發(fā)布深色模式,媒體和網(wǎng)友評論的口吻都是“終于來了”,“遲到的深色模式”。

對于 iOS 程序猿們而言,如何適配 iOS13 系統(tǒng)的深色模式呢?我推薦從以下幾個方面去做適配,推薦參考項目 QPlayer 。
運用系統(tǒng) API
如何運用系統(tǒng) API,請閱讀這篇文章《iOS13 快速讀懂深色模式 API》。
顏色相關(guān)的適配
- 不同模式的適配主要涉及顏色和圖片兩個方面的適配。
- 其中顏色適配,包括相關(guān)背景色和字體顏色。
- 當系統(tǒng)模式切換的時候,我們不需要如何操作,系統(tǒng)會自動渲染頁面,只需要做好不同模式的顏色和圖片即可。
UIColor
-
iOS13之前UIColor只能表示一種顏色,從iOS13開始UIColor是一個動態(tài)的顏色,在不同模式下可以分別代表不同的顏色。 - 下面是
iOS13系統(tǒng)提供的動態(tài)顏色種類,使用以下顏色值,在模式切換時,則不需要做特殊處理。
@interface UIColor (UIColorSystemColors)
#pragma mark System colors
@property (class, nonatomic, readonly) UIColor *systemRedColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemGreenColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemBlueColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemOrangeColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemYellowColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemPinkColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemPurpleColor API_AVAILABLE(ios(9.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemTealColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemIndigoColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
// 灰色種類, 在Light模式下, systemGray6Color更趨向于白色
@property (class, nonatomic, readonly) UIColor *systemGrayColor API_AVAILABLE(ios(7.0), tvos(9.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *systemGray2Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray3Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray4Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray5Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGray6Color API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
#pragma mark Foreground colors
@property (class, nonatomic, readonly) UIColor *labelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *secondaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *tertiaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *quaternaryLabelColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
// 系統(tǒng)鏈接的前景色
@property (class, nonatomic, readonly) UIColor *linkColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
// 占位文字的顏色
@property (class, nonatomic, readonly) UIColor *placeholderTextColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
// 邊框或者分割線的顏色
@property (class, nonatomic, readonly) UIColor *separatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
@property (class, nonatomic, readonly) UIColor *opaqueSeparatorColor API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
#pragma mark Background colors
@property (class, nonatomic, readonly) UIColor *systemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *systemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemGroupedBackgroundColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
#pragma mark Fill colors
@property (class, nonatomic, readonly) UIColor *systemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *secondarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *tertiarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
@property (class, nonatomic, readonly) UIColor *quaternarySystemFillColor API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, watchos);
#pragma mark Other colors
// 這兩個是非動態(tài)顏色值
@property(class, nonatomic, readonly) UIColor *lightTextColor API_UNAVAILABLE(tvos); // for a dark background
@property(class, nonatomic, readonly) UIColor *darkTextColor API_UNAVAILABLE(tvos); // for a light background
@property(class, nonatomic, readonly) UIColor *groupTableViewBackgroundColor API_DEPRECATED_WITH_REPLACEMENT("systemGroupedBackgroundColor", ios(2.0, 13.0), tvos(13.0, 13.0));
@property(class, nonatomic, readonly) UIColor *viewFlipsideBackgroundColor API_DEPRECATED("", ios(2.0, 7.0)) API_UNAVAILABLE(tvos);
@property(class, nonatomic, readonly) UIColor *scrollViewTexturedBackgroundColor API_DEPRECATED("", ios(3.2, 7.0)) API_UNAVAILABLE(tvos);
@property(class, nonatomic, readonly) UIColor *underPageBackgroundColor API_DEPRECATED("", ios(5.0, 7.0)) API_UNAVAILABLE(tvos);
@end
- 上面系統(tǒng)提供的這些顏色種類,根本不能滿足我們正常開發(fā)的需要,大部分的顏色值也都是自定義。
- 系統(tǒng)也為我們提供了創(chuàng)建自定義顏色的方法。
@available(iOS 13.0, *)
public init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)
在 Objective-C 中
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- 上面的方法接受一個閉包 (
block) 。 - 當系統(tǒng)在
LightMode和DarkMode之間相互切換時就會自動觸發(fā)此回調(diào)。 - 回調(diào)返回一個
UITraitCollection,可根據(jù)改對象判斷是那種模式。
fileprivate func getColor() -> UIColor {
return UIColor { (collection) -> UIColor in
if (collection.userInterfaceStyle == .dark) {
return UIColor.red
}
return UIColor.green
}
}
除了上述兩個方法之外,UIColor 還增加了一個實例方法。
// 通過當前 traitCollection 得到對應(yīng) UIColor
@available(iOS 13.0, *)
open func resolvedColor(with traitCollection: UITraitCollection) -> UIColor
CGColor
-
UIColor只是設(shè)置背景色和文字顏色的類,可以動態(tài)的設(shè)置。 - 可是如果是需要設(shè)置類似邊框顏色等屬性時,又該如何處理呢?
- 設(shè)置上述邊框?qū)傩?,需要用?
CGColor類,但是在iOS13中CGColor并不是動態(tài)顏色值,只能表示一種顏色。 - 在監(jiān)聽模式改變的方法中
traitCollectionDidChange,根據(jù)不同的模式進行處理。
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// 每次模式改變的時候, 這里都會執(zhí)行
if (previousTraitCollection?.userInterfaceStyle == .dark) {
redView.layer.borderColor = UIColor.red.cgColor
} else {
redView.layer.borderColor = UIColor.green.cgColor
}
}
圖片適配
- 在
iOS中,圖片基本都是放在Assets.xcassets里面,所以圖片的適配,我們就相對麻煩一些了。 - 正常情況下都是下面這中處理方式。

- 需要適配不同模式的情況下,需要兩套不同的圖片,并做如下設(shè)置。
- 在設(shè)置
Appearances時,我們選擇Any, Dark就可以了 (只需要適配深色模式和非深色模式)。

適配相關(guān)
當前頁面模式
- 原項目中如果沒有適配
Dark Mode,當你切換到Dark Mode后,你可能會發(fā)現(xiàn)有些部分頁面的顏色自動適配了。 - 未設(shè)置過背景顏色或者文字顏色的組件,在
Dark Mode模式下,就是黑色的。 - 這里我們就需要真對該單獨
App強制設(shè)置成Light Mode模式。
// 設(shè)置改屬性, 只會影響當前的視圖, 不會影響前面的 controller 和后續(xù) present 的 controller
@available(iOS 13.0, *)
open var overrideUserInterfaceStyle: UIUserInterfaceStyle
// 使用示例
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// 每次模式改變的時候, 這里都會執(zhí)行
if (previousTraitCollection?.userInterfaceStyle == .dark) {
// 在Dark模式下, 強制改成Light模式
overrideUserInterfaceStyle = .light
}
}
強制項目的顯示模式
- 上面這種方式只能針對某一個頁面修改,如果需要對整個項目禁用
Dark模式。 - 可以通過修改
window的overrideUserInterfaceStyle屬性。 - 在
Xcode11創(chuàng)建的項目中,window從AppDelegate移到SceneDelegate中,添加下面這段代碼,就會做到全局修改顯示模式。
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.overrideUserInterfaceStyle = .light
在之前的項目中,可以在 AppDelegate 設(shè)置如下代碼:
window.overrideUserInterfaceStyle = .light
我創(chuàng)建的項目,上述代碼的確會強制改變當前的模式,但是狀態(tài)欄的顯示不會被修改。
終極方案
- 需要在
info.plist文件中添加User Interface Style配置,并設(shè)置為Light。
<key>UIUserInterfaceStyle</key>
<string>Light</string>
問題又來了,即使做了上面的修改,在 React Native 中,狀態(tài)欄的依然是根據(jù)不同的模式顯示不同的顏色,該如何處理?
Status Bar 更新
在 iOS13 中蘋果對于 Status Bar 也做了部分修改,在 iOS13 之前
public enum UIStatusBarStyle : Int {
case `default` // 默認文字黑色
@available(iOS 7.0, *)
case lightContent // 文字白色
}
從 iOS13 開始 UIStatusBarStyle 一共有三種狀態(tài)
public enum UIStatusBarStyle : Int {
case `default` // 自動選擇黑色或白色
@available(iOS 7.0, *)
case lightContent // 文字白色
@available(iOS 13.0, *)
case darkContent // 文字黑色
}
在 React Native 的代碼中,設(shè)置狀態(tài)欄的顏色為黑色,代碼如下:
<StatusBar barStyle={'dark-content'} />
- 上面這段代碼在
iOS13系統(tǒng)的手機中是無效的。 - 雖然上面的代碼中設(shè)置了
dark-content模式,但是在iOS原生代碼中dark-content實際是 `UIStatusBarStyleDefault。 - 在文件
RCTStatusBarManager.m中
RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
@"default": @(UIStatusBarStyleDefault),
@"light-content": @(UIStatusBarStyleLightContent),
@"dark-content": @(UIStatusBarStyleDefault),
}), UIStatusBarStyleDefault, integerValue);
修改上面代碼即可
@"dark-content": @(@available(iOS 13.0, *) ? UIStatusBarStyleDarkContent : UIStatusBarStyleDefault),
iOS13 其他更新
Apple 登錄
Sign In with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year.
- 如果
APP支持三方登陸 (Facbook、Google、微信、QQ、支付寶等),就必須支持Apple登陸,且要放置前邊。 - 至于
Apple登錄按鈕的樣式,建議支持使用Apple提供的按鈕樣式,已經(jīng)適配各類設(shè)備,可參考 Sign In with Apple 。
LaunchImage
即將被廢棄的 LaunchImage。
- 從
iOS 8的時候,蘋果就引入了LaunchScreen,我們可以設(shè)置LaunchScreen來作為啟動頁。 - 現(xiàn)在你還可以使用
LaunchImage來設(shè)置啟動圖,但是隨著蘋果設(shè)備尺寸越來越多,適配顯然相對麻煩一些。 - 使用
LaunchScreen的話,情況會變的很簡單,LaunchScreen是支持AutoLayout和SizeClass的,所以適配各種屏幕都不在話下。 - ??從2020年4月開始,所有
App將必須提供LaunchScreen,而LaunchImage即將退出歷史舞臺。
UIWebView
'UIWebView' was deprecated in iOS 12.0: No longer supported; please adopt WKWebView.
從 iOS 13 開始也不再支持 UIWebView 控件了,請盡快采用 WKWebView。
@available(iOS, introduced: 2.0, deprecated: 12.0, message: "No longer supported; please adopt WKWebView.")
open class UIWebView : UIView, NSCoding, UIScrollViewDelegate { }