在
macOS 10.14
中,苹果
在系统本身样式(Light (aqua) appearance
)基础上推出了暗黑
模式(dark appearance
),这种模式下可以更突出显示应用窗口中的内容
,让用户的关注焦点聚集在App本身的视图
中以便获取更佳的视觉体验.
关于AppKit中的系统视图,苹果默认已经进行了暗黑模式
适配升级,但对于许多自定义的View
,还是需要我们花一点点时间处理的.
0x00: 关于 NSAppearance
在macOS 10.9+
的时候,苹果就提供了NSAppearance这个类
来协助AppKit
管理App的UI控件
. NSAppearance
决定着AppKit
如何渲染每个UI控件的效果
,尤其是与颜色
或者图片
相关的部分.
- App启动后会获取系统当前样式(
Light Appearance
或者Dark Appearance
). -
NSWindow
会继承App的appearance
效果; -
NSView
会继承其父类或者NSWindow
的appearance
效果; - 开发者可以设置App的
整体
或者部分
的appearance
效果; - 当
Appkit
绘制UI控件
时,会自动将当前的appearance
赋值给控件的appearance(在当前线程中进行)
; -
NSAppearance
会影响系统字体(font)
,颜色(color)
,文本(text)
,图片(image)
的相关绘制路径(draw path
)进而影响显示效果
.
0x01: 颜色适配(NSColor
)
当用户切换
Light / Dark Appearance
时,UI控件的颜色
有着明显不同
的效果.在macOS 10.14
之前我们对于一个控件的颜色值
经常使用硬编码方式
,因此当appearance
变化时,这些硬编码的色值
就难以适应了.
当Appearance
变化时,关于NSColor
的适配苹果官方给出两种
简单并且易于实现的方案:
-
使用带有语义的Color:
那么问题来了,到底什么是带有语义的Color
呢? 看一下苹果官方的原文:
Semantic colors let you specify colors based on their intended usage, rather than on the actual color.
简单的说就是根据使用场景来
描述颜色,而不使用确切的值
来描述颜色.
我们以一个Label 的例子来看一下代码与效果:
设置labelColor
运行效果:
LabelColor 在Dark 和Light 模式下的效果
系统提供的语义Color
可以参考苹果开发者文档中的:UI Element Colors
例如Label相关的有:labelColor
, secondaryLabelColor
,tertiaryLabelColor
,quaternaryLabelColor
等.
除了这些语义Color
之外,系统还提供了一下可适配的Color
,通常都以system+颜色
方式命名.例子如下:
NSColor.systemRed
NSColor.systemBlue
NSColor.systemGray
NSColor.systemPink
-
使用Assets Color:(推荐)
更多时候我们希望能够有更多自己可以定义
的颜色,这时系统提供的语义Color就会显得不够用
,这时我们可以使用Assets Color
,具体操作请参考下图示例:
Assets Color 设置
Appearance 说明
代码调用Assets Color:"Color"是在Assets 中创建的颜色名称
调用Assets Color
运行效果:
Assets Color 运行效果
0x02: 图片适配(NSImage
)
在
App
中图片
是非常重要的UI资源
,为了在合适的Appearance下显示正确的图片
,主要有下面的三种方式.
-
Image Assets
使用Assets Image
与Assets Color
非常相似,具体请参考操作图例:
Assets Image Set
Assets Image 的适配场景(即当下面场景变化时,会Appkit会自动调整Image进行适配
):
- Screen resolution(
屏幕分辨率
):
Appkit
会自动根据当前屏幕的解析度选取最佳的image
进行显示 - Light and dark appearances (
Appearance切换
):
Any Appearance
中的图片会适配macOS
全版本,Light和Dark
仅适用macOS10.14
之后的版本 - High contrast (
高对比度
):
使图片与周边的内容对比根据突出,仅能用于macOS10.14+
之后的版本
-
Template Images
使用模版图片
也是一种常用的适配解决方案,典型的案例就是设置控件的icon(比如一个播放
或者暂停
的按钮).这种方法需要配合使用图片编辑软件(项目中的话通常就是UI设计师来处理)制作图片模版,具体使用仅需两个步骤即可:
- UI设计师需要根据场景设计图片,但需要遵守如下规则:
template 设置规则
需要忽略的部分使用透明背景
需要显示的部分使用
黑色或者部分透明的黑色
-
设置图片的渲染模式为
Template
:
设置图片渲染模式
-
Drawing Handler
使用NSImage
的init(size:flipped:drawingHandler:)
方法可以让Appkit
根据appearance
变化时自动调用drawingHandler
中的代码进行图片创建,从而实现适配效果;
0x03: 自定义View 适配(NSView
)
当改变当前的appearance
时,AppKit
会自动调用NSView
的下面几个方法(根据情况调用
)
- updateLayer()
- draw(_:)
- layout()
- updateConstraints()
这样我们就有机会在变更appearance
时,通过重载
上面的方法来实现自定义view的UI适配工作
,示例代码如下:
override func updateLayer() {
self.layer?.backgroundColor = NSColor.textBackgroundColor.cgColor
// Other updates.
}
注意点!!!
NSColor
会立刻生效,但CGColor
需要App再次启动才会生效!
0x04: 定制App的appearance(NSApp
)
- 设置
NSView
或者NSWindow
的appearance
:
NSView Appearance
注意点!!!
Appearance是存在继承关系的:NSApp
->NSWindow
->NSView
- 通过代码方式设置
NSView
的appearance
:
class MyContentView : NSView {
func adoptAquaAppearance()
self.appearance = NSAppearance(named: .aqua)
}
}
- 设置
NSApp
的Appearance
:
NSApp.appearance = NSAppearance(named: .darkAqua)
0x05: Visual Effect View
关于NSVisualEffectView
的Appearance
适配,苹果官方建议采用根据使用明确场景语义枚举
.例如在一个popOver
的窗口中,推荐使用NSVisualEffectView.Material.popover
,这样系统就根据appearance
变化自动选择合适的效果了.同时系统也废弃了如下的枚举:
- NSVisualEffectView.Material.light
Deprecated
- NSVisualEffectView.Material.dark
Deprecated
- NSVisualEffectView.Material.mediumLight
Deprecated
- NSVisualEffectView.Material.ultraDark
Deprecated
0x06: 当appearance 切换时,应避免耗时操作
当切换系统的Appearance
时,AppKit
会同时更新UI控件
,这部分工作通常都是自动完成的.但有时也会调用开发者编写的代码,例如你使用了NSImage
的draw handler 方式
创建图片对象,又或者使用了KVO监听一个视图或者窗口的effectiveAppearance
属性,因此请需要注意下面几点:
- 尽可能快的更新UI;
- 不要执行与appearance变更无关的任务;
- appearance变化时AppKit会自动添加过渡效果动画,但如果你的更新UI代码任务过重,AppKit将会丢弃过渡效果动画!
0x07: one more thing
为了考虑兼容macOS10.14
之前的App
版本,但又想支持Dark Appearance
的效果,那么可以在Info.plist
中添加 NSRequiresAquaSystemAppearance
key,并设置值为true
即可.
这样做的前提是要保证App在macOS10.14的Dark Mode下可以正常适配UI效果~.
网友评论