在iOS13之后,苹果系统及其应用就开始全面适配和支持黑暗模式了;为了增加iOS的一体性与用户的体验,苹果官方又进一步强烈建议所有上架Apple Store的App进行DarkMode的适配。
因为适配黑暗模式对于设计和开发者来说都存在一定的工作量,所以对于还没有完全准备适配DarkMode的App来说,需要一个过渡阶段以保证App的某些系统适配颜色不受影响,所以苹果提供了一个配置以关闭全局暗黑模式,具体如下:
UIUserInterfaceStyle
key 名字为 User Interface Style
值为String,UIUserInterfaceStyle
key 的值设置为 Light
创建一个颜色的Assets,调整Appearance为Any,Dark 然后就可以在里面配置不同Appearance的颜色啦~在assets添加自定义颜色是从iOS11开始的所以对老版本的iOS兼容不好 ,建议使用代码适配颜色;
Assets的适配图片不会影响老的版本,老版本的iOS会自动识别 Any Appearance,所以一般图片的适配一般采用创建Assets的方式来适配;
在iOS13.0往后,SDK提供了可以根据userInterfaceStyle自动返回指定mode的颜色的接口:
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull trainCollection) {
if ([trainCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
return lightColor;
}else {
return darkColor;
}
}];
通过UITraitEnvironment协议的traitCollection属性公开。UIScreen、UIWindow 、UIViewController、UIPresentationController、 UIView都遵守了UITraitEnvironment协议。
根据这些属性的变化自适应界面的布局、设置特殊属性:UITraitCollection(水平大小,垂直大小,展示的比例,用户界面习惯用法等),可打印出一个View的UITraitCollection如下所示:
其实dynamicColor就是通过trait获取可以获取当前的展示的具体是什么颜色的:
UIColor *dynamicColor = [UIColor systemBackGroundColor];
UITraitCollection *traitCollection = view.traitCollection
UIColor *resolvedColor = [dynamicColor resolvedColorWithTraitCollection:traitCollection];
所以可以在重写drawRect方法的时候 ,UIkit会把当前的环境设置给view的traitCollection,这个时候用systemcolor绘制会使用正确的颜色,模式变化的时候会调用setNeedsDisplay触发drawRect方法。
常规来看大家以为UITraitCollection是一个单例,大家根据他去做事情,但其实UITraitCollection贯穿了整个app的视图层级,每个层级都有自己的raitcollection,每个层级根据自己的traitcollection做事情。
当我们添加一个view,即addsubview的时候会将上一层的traitCollection继承过来,就可以进行自己的mode的展示了:
官方文档中,UIView、UIViewController、UIPresentationController对应的模式切换会调用的方法如下图所示,在下面方法处理的渲染是准确的,系统也会在切换mode的时候调用:
官方表示layout方法是我们使用traitcolletion的最好时机,把外观代码放到任何一个即可,不要做不必要的工作,可以调用他们的补充方法如 setNeedsUpdateConstraints、setNeedsLayout触发它们。
有些情况需特殊处理,比如CALayer 和 CGColor,他们不能识别DarkMode, 这时我们就需要需要通过traitcollection来处理。
通过CALayer添加上的view拿到它的traitcollection 获取当前需要展示的颜色 然后转成CGColor:
CALayer *layer = [CALayer new];
UITraitCollection *traitCollection = view.traitCollection;
UIColor *resolvedColor = [[UIColor labelColor] resolvedColorWithTraitCollection:traitCollection];
layer.borderColor = resolvedColor.CGColor;
在performAsCurrent方法里面直接拿到当前的CGColor:
CALayer *layer = [CALayer new];
UITraitCollection *traitCollection = view.traitCollection;
[traitCollection performAsCurrentTraitCollection:^{
layer.borderColor = [UIColor labelColor].CGColor;
}];
根据当前view的traitCollection判断,有时候view的traitCollection不一定和traitCollection.current保持一致,我们需要把当前的保存好赋值完成后设回去,知道mode的变化才能更好地控制我们需要改变的东西,mode变化会走traitCollectionDidChange方法,横屏等操作也会走这个方法,建议先判断是否是颜色的变化:
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
xxx
}
}
StatusBar
:之前分为default 和lightContent两种类型 ,现在darkcontent表示default样式下的light模式下的导航栏样式,现在default变成了dynamic类型,根据mode变化进行切换。
UIActivityIndicatorView
:去掉了之前的各种类型 ,现在是设置大小 ,颜色属性自己控制
上述两个控件也是iOS13之后做的更改,以更好的适配黑暗模式。
有时候我们有的界面不想进行黑暗模式的变换应该怎么办呢,其实UIViewController与UIView 都新增一个属性 overrideUserInterfaceStyle,
将 overrideUserInterfaceStyle
设置为对应的模式,则强制限制该元素与其子元素以设置的模式进行展示,不跟随系统模式改变进行改变;
黑暗模式的适配更多的是苹果为了提升用户体验和应用一体性所提出的要求,在苹果的官方文档中也提到未适配的应用将会存在上架审核不通过的风险。