DSFAppearanceManager

一个用于简化 macOS 外观值并检测设置更改的类 (Swift/Objective-C)。

支持 macOS 10.11 及更高版本,并在较旧的系统上提供合理的后备方案,以减少代码中的 #available/@available 检查。

为什么需要它?

如果在 macOS 应用程序中执行自定义绘图,请务必在执行绘图时遵守用户的显示和辅助功能设置,以便进行相应的调整。

  1. 在不同的 macOS 系统中,检索这些值的方法可能不同(并且在较早的系统中很难可靠地提取)。这个库封装了所有这些不一致之处,以便你的代码可以保持更简洁。
  2. 当用户更改他们的设置时(例如,当系统自动更改浅色/深色模式时),我希望我的应用程序收到更改通知,以便我可以更新绘图以匹配新的设置。

外观

DSFAppearanceManager 具有许多属性来简化 macOS 外观设置。

可用属性

以下是 DSFAppearanceManager 上可用的静态属性。

属性 描述
IsDark UI 当前是否以深色显示
IsDarkMenu 菜单和 Dock 当前是否以深色显示
AccentColor 当前强调色
HighlightColor 当前高亮颜色
AquaVariant 当前的 Aqua 变体
IncreaseContrast 用户的“增加对比度”辅助功能设置
DifferentiateWithoutColor 用户的“不使用颜色区分”辅助功能设置
ReduceTransparency 用户的“减少透明度”辅助功能设置
InvertColors 用户的“反转颜色”辅助功能设置
ReduceMotion 用户的“减少动态效果”辅助功能设置
AutoplayAnimatedImages 用户的“自动播放动画图像”辅助功能设置 (macOS 14+)

因此,例如,要获取当前 macOS 的高亮颜色,请调用 DSFAppearanceManager.HighlightColor

更改检测

您可以请求在外观设置更改时收到通知。当外观更改时,macOS 会自动调用某些方法:-

NSView

NSViewController

但在某些情况下,您需要自己管理此操作。 这就是使用 ChangeDetector 类的地方。

声明一个 DSFAppearanceManager.ChangeDetector() 类型的变量

private let appearanceChangeDetector = DSFAppearanceManager.ChangeDetector()

...并设置回调块。 请注意,保证此回调在主线程上调用。

appearanceChangeDetector.appearanceChangeCallback = { [weak self] change in
   // Handle the change here.
   // `change` contains the _types_ of change(s) that occurred. This might be theme, accent, contrastOrAccessibility etc
   let currentHighlightColor = DSFAppearanceManager.HighlightColor
   ...
}

更改检测类型

change 对象指示发生的更改类型。

更改类型 描述
theme 系统外观(例如,深色/浅色)已更改
accent 用户更改了强调色(例如,强调色/高亮色)
aquaVariant 对于较旧的 macOS 版本,变体(蓝色、石墨色)
systemColors 用户更改了系统颜色
finderLabelColorsChanged 用户更改了 Finder 标签颜色
accessibility 辅助功能显示设置已更改
autoplayAnimatedImages 自动播放动画图像已更改

请注意,更改检测类会对更改进行去抖动,以减少发生更改时的回调次数。 回调块中传递的 change 对象包含发生的一组更改。

Objective-C 支持

@interface ViewController ()
@property(nonatomic, strong) DSFAppearanceManagerChangeDetector* detector;
@end

@implementation ViewController
- (void)viewDidAppear {
   [super viewDidAppear];
   [self setDetector: [[DSFAppearanceManagerChangeDetector alloc] init]];
   [[self detector] setAppearanceChangeCallback:^(DSFAppearanceManagerChange * _Nonnull change) {
      // Change detected! Do something to update display
   }];
}
@end

集中式通知 (DSFAppearanceCache)

如果您有很多需要更新的小类,那么将更改通知集中在一个公共位置可能更有效。

该库提供了一个默认的全局(懒加载)DSFAppearanceCache.shared 对象实例供您使用,或者您可以自己创建和管理一个。

外观缓存提供了两种接收外观更新通知的机制。

直接在缓存中注册更新

您可以通过使您的对象符合 DSFAppearanceCacheNotifiable 协议来注册一个对象以接收外观更新。 该对象在缓存对象中被弱引用。

示例

class LevelGauge: CustomLayer, DSFAppearanceCacheNotifiable {
   init() {
      DSFAppearanceCache.shared.register(self)
   }

   deinit {
      DSFAppearanceCache.shared.deregister(self)
   }

   func appearanceDidChange() {
      // Update the object
   }
}

通过 NotificationCenter 注册更新

更改中心对象 DSFAppearanceCacheNotificationCenter.default 上生成通知。

通知名称: DSFAppearanceCache.ChangeNotificationName

您可以使用标准的 addObserver 机制注册通知。

示例

self.observer = NotificationCenter.default.addObserver(
   forName: DSFAppearanceCache.ChangeNotificationName,
   object: DSFAppearanceCache.shared,
   queue: OperationQueue.main) { _ in
      // Do something with the change
}

确保你的 'appearanceDidChange' 中的代码快速执行!

附加支持

NSView 外观绘制

DSFAppearanceManager 提供了 NSView 的扩展,以方便自动处理视图的有效绘制外观。

func drawRect(_ dirtyRect: CGRect) {
   ...
   self.usingEffectiveAppearance {
      // Requests for dynamic colors etc. within the block will automatically use the correct appearance for the view.
   }
}

构建您自己的动态 NSColor

如果您无法使用 Assets.xcassets 来存储您的动态 NSColors(或者您想将应用程序的配置移到代码中),您会发现默认的 NSColor 对自动处理浅色/深色模式更改的支持不多。

Dusk 是一个小型 Swift 框架,旨在帮助支持 macOS 上的深色模式。 它提供了一个 NSColor 子类 (DynamicColor),该子类在需要时自动提供浅色/深色模式变体。

lazy var c1 = DynamicColor(name: "uniqueColorName") { (appearance) in 
    // return the color to use for this appearance
}

let c1 = DynamicColor(name: "uniqueColorName", lightColor: NSColor.white, darkColor: NSColor.black)

并且因为 DynamicColor 继承自 NSColor,所以可以在任何可以使用 NSColor 的地方使用它。

感谢!

ChimeHQ 开发了出色的 动态 NSColor 子类

许可

MIT License

Copyright (c) 2023 Darren Ford

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.