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 infrastructure

获取当前模式

提供两种方式设置手机当前外观模式

  • 设置 --> 显示与亮度
  • 控制中心, 长按亮度调节按钮

获取当前模式

我们需要选获取到当前出于什么模式, 在根据不同的模式进行适配, iOS 13中新增了获取当前模式的API

Swift

// 获取当前模式
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语言

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中

+ (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)
  • 当系统在LightMode和DarkMode之间相互切换时就会自动触发此回调
  • 回调返回一个UITraitCollection, 可根据改对象判断是那种模式
fileprivate func getColor() -> UIColor {
 return UIColor { (collection) -> UIColor in
  if (collection.userInterfaceStyle == .dark) {
   return UIColor.red
  }
  return UIColor.green
 }
}

除了上述两个方法之外, UIColor还增加了一个实例方法

// 通过当前traitCollection得到对应UIColor
@available(iOS 13.0, *)
open func resolvedColor(with traitCollection: UITraitCollection) -> UIColor

CGColor

  • UIColor只是设置背景色和文字颜色的类, 可以动态的设置
  • 可是如果是需要设置类似边框颜色等属性时, 又该如何处理呢
  • 设置上述边框属性, 需要用到CGColor类, 但是在iOS13中CGColor并不是动态颜色值, 只能表示一种颜色
  • 在监听模式改变的方法中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里面, 所以图片的适配, 我们就相对麻烦一些了
正常情况下都是下面这中处理方式

需要适配不同模式的情况下, 需要两套不同的图片, 并做如下设置

在设置Appearances时, 我们选择Any, Dark就可以了(只需要适配深色模式和非深色模式)

适配相关

当前页面模式

原项目中如果没有适配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模式

可以通过修改window的overrideUserInterfaceStyle属性

在Xcode11创建的项目中, window从AppDelegate移到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支持三方登陆(Facbook、Google、微信、QQ、支付宝等),就必须支持苹果登陆,且要放前边
至于Apple登录按钮的样式, 建议支持使用Apple提供的按钮样式,已经适配各类设备, 可参考Sign In with Apple

LaunchImage

即将被废弃的LaunchImage

  • 从iOS 8的时候,苹果就引入了LaunchScreen,我们可以设置LaunchScreen来作为启动页。
  • 现在你还可以使用LaunchImage来设置启动图, 但是随着苹果设备尺寸越来越多, 适配显然相对麻烦一些
  • 使用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 { }

到此这篇关于iOS13适配深色模式(Dark Mode)的实现的文章就介绍到这了,更多相关iOS13适配深色模式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • jQuery实现右侧显示可向左滑动展示的深色QQ客服效果代码

    本文实例讲述了jQuery实现右侧显示可向左滑动展示的深色QQ客服效果代码.分享给大家供大家参考,具体如下: 这是一个黑色的QQ客服代码,显示在网页右侧,点击后会向左展开QQ客服的完整内容,适用的网站范围广,以前发过不少的在线客服,每一款都风格不同,你可以在网页特效栏目搜索"在线客服"看下有你需要的不. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/jquery-right-show-qq-online-kf-codes/ 具体代码如

  • Flutter适配深色模式的方法(DarkMode)

    1.瞎叨叨 也不知道写点什么,本来想写写Flutter的集成测试.因为前一阵子给flutter_deer写了一套,不过感觉也没啥内容,写不了几句话就放弃了.(其实本篇内容也不多...) 那就写写最近在做的事情.没错,就是文章标题提到的适配深色模式(DarkMode),也可以说是实现夜间模式的功能.相信许多iOS的同学最近都比较关注,毕竟iOS 13上个月推送更新了. 说适配的原因是因为在iOS 13 和 Android 10系统上它都属于新特性.适配的目的是为了达到应用的主题随着系统主题模式的切

  • 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

  • Flutter深色模式适配的实现

    一.简介 Flutter的深色模式以及跟随系统设置比较简单,我感觉需要注意的是开发过程中尽量使用Theme中的颜色与样式,开发过程中遇到的比较大的坑就是provider的一些问题,可能是因为我用的版本新一些,网上找了很多文章,总会遇到一些问题.本文的深色模式适配是通过修改themeMode来实现的,供诸位有缘人参考. 二.环境介绍 1. Flutter: 2.0.3 2. Dart: 2.12.0 3. provider: 5.0.0 状态管理,用于运行时切换主题 4. shared_prefe

  • 浅谈iOS开发如何适配暗黑模式(Dark Mode)

    暗黑模式 原理 将同一个资源,创建出两种模式的样式.系统根据当前选择的样式,自动获取该样式的资源 每次系统更新样式时,应用会调用当前所有存在的元素调用对应的一些重新方法,进行重绘视图,可以在对应的方法做相应的改动 资源文件适配 创建一个Assets文件(或在现有的Assets文件中) 新建一个图片资源文件(或者颜色资源文件.或者其他资源文件) 选中该资源文件, 打开 Xcode ->View ->Inspectors ->Show Attributes Inspectors (或者Opt

  • iOS13适配的实现方法

    1.私有KVC [self setValue:baseTabBar forKey:@"tabBar"]; //正常 [_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];///崩溃 [_textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@"_placeholderLabel.fo

  • Android显示富文本+夜间深色模式

    目录 前言 方案一: 存在问题: 方法二: 富文本内容: 演示效果: 总结 前言 在工作中有遇到这样的需求,需要把hmtl的富文本内容,进行深色模式适配.原先的富文本内容是在直接在webview上进行展示. 解决思路:替换html中的内容色值. 方案一: 直接使用replace进行字符串替换,当时是去判断.标签,例如下代码 newText.replace("<p>", "<p><span style=\"color: rgb(51, 5

  • Android如何快速适配暗黑模式详解

    直接上代码 public class DarkModeUtils { public static final String KEY_CURRENT_MODEL = "night_mode_state_sp"; private static int getNightModel(Context context) { SharedPreferences sp = context.getSharedPreferences(KEY_CURRENT_MODEL, Context.MODE_PRIV

  • iOS13 适配和Xcode11.0踩坑小结

    iOS13中presentViewController的问题 更新了Xcode11.0 beta之后,在iOS13中运行代码发现presentViewController和之前弹出的样式不一样. 会出现这种情况是主要是因为我们之前对UIViewController里面的一个属性,即modalPresentationStyle(该属性是控制器在模态视图时将要使用的样式)没有设置需要的类型.在iOS13中modalPresentationStyle的默认改为UIModalPresentationAu

  • Android界面一键变灰开发深色适配模式编程示例

    目录 深色主题工具类 background_color公用背景色 values/colors.xml 的代码 values-night/colors.xml 的代码 Android 界面一键变灰 java kotlin 深色主题工具类 package com.example.kotlindemo.utils import android.content.Context import android.content.res.Configuration import androidx.appcomp

  • iOS13原生端适配攻略(推荐)

    随着iOS 13的发布,公司的项目也势必要着手适配了.现汇总一下iOS 13的各种坑 1. KVC访问私有属性 这次iOS 13系统升级,影响范围最广的应属KVC访问修改私有属性了,直接禁止开发者获取或直接设置私有属性.而KVC的初衷是允许开发者通过Key名直接访问修改对象的属性值,为其中最典型的 UITextField 的 _placeholderLabel.UISearchBar 的 _searchField. 造成影响:在iOS 13下App闪退 错误代码: // placeholderL

随机推荐