iOS13適配深色模式(Dark Mode)

iOS 13

  • 原文博客地址: iOS13適配深色模式(Dark Mode)
  • 好像大概也許是一年前, Mac OS系統發佈了深色模式外觀, 看着挺刺激, 時至今日用着也還挺爽的
  • 終於, 隨着iPhone11等新手機的發售, iOS 13系統也正式發佈了, 伴隨着手機版的深色模式也出如今了大衆視野
  • 咱們這些iOS程序猿也有事情作了, 原有項目適配iOS13系統, 適配Dark Mode深色模式
  • 雖然如今並無要求強制適配Dark Mode, 可是DarK適配卻也迫在眉睫

Apps on iOS 13 are expected to support dark mode Use system colors and materials Create your own dynamic colors and images Leverage flexible infrastructurehtml

獲取當前模式

提供兩種方式設置手機當前外觀模式ios

  • 設置 --> 顯示與亮度
  • 控制中心, 長按亮度調節按鈕

獲取當前模式

咱們須要選獲取到當前出於什麼模式, 在根據不一樣的模式進行適配, iOS 13中新增了獲取當前模式的APIswift

Swiftapi

// 獲取當前模式
let currentMode = UITraitCollection.current.userInterfaceStyle
if (currentMode == .dark) {
    print("深色模式")
} else if (currentMode == .light) {
    print("淺色模式")
} else {
    print("未知模式")
}

open var userInterfaceStyle: UIUserInterfaceStyle { get } 

// 全部模式
public enum UIUserInterfaceStyle : Int {
    // 未指明的
    case unspecified
    // 淺色模式
    case light
    // 深色模式
    case dark
}
複製代碼

OC語言bash

if (@available(iOS 13.0, *)) {
    UIUserInterfaceStyle mode = UITraitCollection.currentTraitCollection.userInterfaceStyle;
    if (mode == UIUserInterfaceStyleDark) {
        NSLog(@"深色模式");
    } else if (mode == UIUserInterfaceStyleLight) {
        NSLog(@"淺色模式");
    } else {
        NSLog(@"未知模式");
    }
}

// 各類枚舉值
typedef NS_ENUM(NSInteger, UIUserInterfaceStyle) {
    UIUserInterfaceStyleUnspecified,
    UIUserInterfaceStyleLight,
    UIUserInterfaceStyleDark,
} API_AVAILABLE(tvos(10.0)) API_AVAILABLE(ios(12.0)) API_UNAVAILABLE(watchos);
複製代碼

監聽系統模式的變化

iOS13系統中, UIViewController遵循了兩個協議: UITraitEnvironmentUIContentContainer協議微信

UITraitEnvironment協議中, 爲咱們提供了一個監聽當前模式變化的方法閉包

@protocol UITraitEnvironment <NSObject>
// 當前模式
@property (nonatomic, readonly) UITraitCollection *traitCollection API_AVAILABLE(ios(8.0));

// 重寫該方法監聽模式的改變
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection API_AVAILABLE(ios(8.0));
@end
複製代碼
public protocol UITraitEnvironment : NSObjectProtocol {
    // 當前模式
    @available(iOS 8.0, *)
    var traitCollection: UITraitCollection { get }

    // 重寫該方法監聽模式的改變
    @available(iOS 8.0, *)
    func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
}


// 使用方法
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    
    // 每次模式改變的時候, 這裏都會執行
    print("模式改變了")
}
複製代碼

顏色相關適配

  • 不一樣模式的適配主要涉及顏色和圖片兩個方面的適配
  • 其中顏色適配, 包括相關背景色和字體顏色
  • 當系統模式切換的時候, 咱們不須要如何操做, 系統會自動渲染頁面, 只須要作好不一樣模式的顏色和圖片便可

UIColor

  • iOS13以前UIColor只能表示一種顏色,從iOS13開始UIColor是一個動態的顏色,在不一樣模式下能夠分別表明不一樣的顏色
  • 下面是iOS13系統提供的動態顏色種類, 使用如下顏色值, 在模式切換時, 則不須要作特殊處理
@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);
// 系統連接的前景色
@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
// 這兩個是非動態顏色值
@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
複製代碼
  • 上面系統提供的這些顏色種類, 根本不能知足咱們正常開發的須要, 大部分的顏色值也都是自定義
  • 系統也爲咱們提供了建立自定義顏色的方法
@available(iOS 13.0, *)
public init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)
複製代碼

在OC中app

+ (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)
  • 當系統在LightModeDarkMode之間相互切換時就會自動觸發此回調
  • 回調返回一個UITraitCollection, 可根據改對象判斷是那種模式
fileprivate func getColor() -> UIColor {
    return UIColor { (collection) -> UIColor in
        if (collection.userInterfaceStyle == .dark) {
            return UIColor.red
        }
        return UIColor.green
    }
}
複製代碼

除了上述兩個方法以外, UIColor還增長了一個實例方法ide

// 經過當前traitCollection獲得對應UIColor
@available(iOS 13.0, *)
open func resolvedColor(with traitCollection: UITraitCollection) -> UIColor
複製代碼

CGColor

  • UIColor只是設置背景色和文字顏色的類, 能夠動態的設置
  • 但是若是是須要設置相似邊框顏色等屬性時, 又該如何處理呢
  • 設置上述邊框屬性, 須要用到CGColor類, 可是在iOS13CGColor並非動態顏色值, 只能表示一種顏色
  • 在監聽模式改變的方法中traitCollectionDidChange, 根據不一樣的模式進行處理
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    
    // 每次模式改變的時候, 這裏都會執行
    if (previousTraitCollection?.userInterfaceStyle == .dark) {
        redView.layer.borderColor = UIColor.red.cgColor
    } else {
        redView.layer.borderColor = UIColor.green.cgColor
    }
}
複製代碼

圖片適配

  • iOS中, 圖片基本都是放在Assets.xcassets裏面, 因此圖片的適配, 咱們就相對麻煩一些了
  • 正常狀況下都是下面這中處理方式

image

  • 須要適配不一樣模式的狀況下, 須要兩套不一樣的圖片, 並作以下設置
  • 在設置Appearances時, 咱們選擇Any, Dark就能夠了(只須要適配深色模式和非深色模式)

image

適配相關

當前頁面模式

  • 原項目中若是沒有適配Dark Mode, 當你切換到Dark Mode後, 你可能會發現, 有些部分頁面的顏色自動適配了
  • 未設置過背景顏色或者文字顏色的組件, 在Dark Mode模式下, 就是黑色的
  • 這裏咱們就須要真對該單獨App強制設置成Light Mode模式
// 設置改屬性, 只會影響當前的視圖, 不會影響前面的controller和後續present的controller
@available(iOS 13.0, *)
open var overrideUserInterfaceStyle: UIUserInterfaceStyle

// 使用示例
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    
    // 每次模式改變的時候, 這裏都會執行
    if (previousTraitCollection?.userInterfaceStyle == .dark) {
        // 在Dark模式下, 強制改爲Light模式
        overrideUserInterfaceStyle = .light
    }
}
複製代碼

強制項目的顯示模式

  • 上面這種方式只能針對某一個頁面修改, 若是須要對整個項目禁用Dark模式
  • 能夠經過修改windowoverrideUserInterfaceStyle屬性
  • Xcode11建立的項目中, windowAppDelegate移到SceneDelegate中, 添加下面這段代碼, 就會作到全局修改顯示模式
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.overrideUserInterfaceStyle = .light
複製代碼

在以前的項目中, 能夠在AppDelegate設置以下代碼字體

window.overrideUserInterfaceStyle = .light
複製代碼

我建立的簡單項目, 上述代碼的確會強制改變當前的模式, 可是狀態欄的顯示不會被修改, 不知道是否是漏了什麼

終極方案

  • 須要在info.plist文件中添加User Interface Style配置, 並設置爲Light
<key>UIUserInterfaceStyle</key>
<string>Light</string>
複製代碼

問題又來了, 即便作了上面的修改, 在React Native中, 狀態欄的依然是根據不一樣的模式顯示不一樣的顏色, 該如何處理嘞?

Status Bar更新

iOS13中蘋果對於Status Bar也作了部分修改, 在iOS13以前

public enum UIStatusBarStyle : Int {
    case `default` // 默認文字黑色

    @available(iOS 7.0, *)
    case lightContent // 文字白色
}
複製代碼

iOS13開始UIStatusBarStyle一共有三種狀態

public enum UIStatusBarStyle : Int {
    case `default` // 自動選擇黑色或白色

    @available(iOS 7.0, *)
    case lightContent // 文字白色
    
    @available(iOS 13.0, *)
    case darkContent // 文字黑色
}
複製代碼

React Native的代碼中, 設置狀態欄的顏色爲黑色, 代碼以下

<StatusBar barStyle={'dark-content'} />
複製代碼
  • 上面這段代碼在iOS13系統的手機中是無效的
  • 雖然上面的代碼中設置了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 其餘更新

蘋果登陸

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支持三方登錄(FacbookGoogle、微信、QQ、支付寶等),就必須支持蘋果登錄,且要放前邊
  • 至於Apple登陸按鈕的樣式, 建議支持使用Apple提供的按鈕樣式,已經適配各種設備, 可參考Sign In with Apple

LaunchImage

即將被廢棄的LaunchImage

  • iOS 8的時候,蘋果就引入了LaunchScreen,咱們能夠設置LaunchScreen來做爲啓動頁。
  • 如今你還可使用LaunchImage來設置啓動圖, 可是隨着蘋果設備尺寸愈來愈多, 適配顯然相對麻煩一些
  • 使用LaunchScreen的話,狀況會變的很簡單,LaunchScreen是支持AutoLayoutSizeClass的,因此適配各類屏幕都不在話下。
  • ⚠️從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 { }
複製代碼

歡迎您掃一掃下面的微信公衆號,訂閱個人博客!

微信公衆號
相關文章
相關標籤/搜索