公司动态
macOS 主题切换库 ThemeKit 实战:从“配色失控“到“3 分钟接入深浅主题“
macOS 主题切换库 ThemeKit 实战从配色失控到3 分钟接入深浅主题【免费下载链接】ThemeKitmacOS theming library项目地址: https://gitcode.com/gh_mirrors/the/ThemeKit如果你维护过一个 macOS 应用大概率经历过这样的场景用户把系统外观从浅色切到深色你的窗口工具栏瞬间变成一半黑一半白标签文字看不清配好的品牌色直接失效。深色模式适配在 iOS 上早就是标配但在 macOS 上一套要改十几个文件的配色方案依然能让一个迭代周期平白多出两天工作量。ThemeKit 就是为解决这个问题而生的轻量级开源库它完全用 Swift 编写既支持 Swift 也支持 Objective-C 工程提供深浅主题一键切换、自定义主题、主题感知的颜色/渐变/图片资源以及自动跟随系统外观的能力。目标很简单——让你的 macOS 应用天生会换肤而不是每次都要人工适配。一、先看痛点系统外观一变界面就失控在引入任何方案之前先明确问题的本质。macOS 应用的颜色来源大概有三个系统控件自带的颜色NSColor.labelColor、windowBackgroundColor等你在代码里写死的常量色值你自己定义的主题色。其中第二类最棘手。NSColor的部分命名颜色在 macOS 10.14 上会跟随系统外观变化但你自己写死的那几十个NSColor(red:...)不会。于是深色模式下浅色背景配上深色文字、或者反过来各种瞎眼搭配就出现了。ThemeKit 的核心思路是把颜色/渐变/图片从代码常量升级为主题感知资产不直接给控件赋值色值而是赋一个会跟随当前主题解析的动态对象。切换主题时所有引用它的界面自动更新无需逐控件刷新。二、认识核心成员四个主题 一个管理器ThemeKit 的 API 面很小Sources/目录下十几个文件就能讲清全部机制ThemeManager.swift——全局主题管理器负责切换、持久化、窗口策略Theme.swift——主题协议一切主题的基座LightTheme/DarkTheme/SystemTheme——内置的浅色、深色、系统跟随主题UserTheme——从.theme文本文件加载的用户主题ThemeColor/ThemeGradient/ThemeImage——主题感知的资产类型。其中SystemTheme是一个特殊存在它不固定外观而是实时解析为系统偏好设置 › 通用 › 外观对应的浅色或深色主题。用户把系统切成深色你的应用自动跟着变你一行判断都不用写。图ThemeKit 官方 Demo 中的主题切换效果动图源文件位于 Imgs/ThemeKit.gif三、两步接入你的 Xcode 工程ThemeKit 提供 CocoaPods 和 Carthage 两种主流集成方式最低支持 macOS 10.10。CocoaPods在 Podfile 中加入use_frameworks! target 你的应用名 do pod macOSThemeKit, ~ 1.2.0 end注意用 CocoaPods 时模块名是macOSThemeKit导入语句相应为import macOSThemeKit。Carthage在 Cartfile 中写入github luckymarmot/ThemeKit然后import ThemeKit即可。如果两种包管理都不想用也可以直接把Sources/目录下的 Swift 源文件拖进工程效果一样。四、最小可用一行代码切换主题集成完成后在AppDelegate的启动回调里写下第一行主题代码func applicationWillFinishLaunching(_ notification: Notification) { // 应用深色主题整个应用即刻切换为深色外观 ThemeManager.darkTheme.apply() }apply()会做三件事把主题设为当前主题、把主题标识写入NSUserDefaults下次启动自动恢复、按窗口策略重绘所有符合条件窗口的外观。也就是说主题选择是持久化的用户不用每次启动都重新选。如果你希望默认跟随系统外观把上面一行换成ThemeManager.systemTheme.apply()即可。想让用户自己选可以这样列出所有可用主题for theme in ThemeManager.shared.themes { // themes 包含内置三主题 你的自定义主题 用户主题 print(\(theme.displayName) id: \(theme.identifier)) }五、窗口主题策略精确控制哪些窗口被换肤不是所有窗口都适合被动换肤——比如NSPanel、弹窗这类浮动窗口可能希望保持独立外观。ThemeManager提供了一套WindowThemePolicy枚举ThemeManager.shared.windowThemePolicy .themeAllWindows // 或只主题化指定窗口类 // .themeSomeWindows(windowClasses: [MainWindow.self]) // 或排除指定窗口类 // .doNotThemeSomeWindows(windowClasses: [NSPanel.self])默认策略是themeAllWindows。被排除的窗口仍然可以通过NSWindow扩展手动主题化window.theme()会无条件刷新外观window.themeIfCompliantWithWindowThemePolicy()则会先校验策略。这个粒度让全局换肤和局部定制可以共存。六、自定义主题的两条路径路径 A原生 Swift 类适合复杂主题新建一个NSObject子类并遵守Theme协议实现四个必填属性即可class MyTheme: NSObject, Theme { static var identifier com.example.MyTheme var identifier: String MyTheme.identifier var displayName: String 我的自定义主题 var shortDisplayName: String 自定义 var isDarkTheme: Bool true }声明了ThemeColor对应扩展里的颜色方法后这个类会自动出现在ThemeManager.shared.themes列表中——ThemeKit 通过运行时扫描自动发现所有遵守协议的类无需手动注册。路径 B.theme文本文件适合设计师/最终用户在Application Support/{你的Bundle ID}/Themes目录下放一个文本文件ThemeKit 会热加载它// 主题信息 displayName 优雅深色 identifier com.example.ElegantDark darkTheme true // 颜色定义 brandColor rgb(30, 144, 255) backgroundColor rgb(18, 18, 18) textColor rgb(220, 220, 220) // 渐变与图片 mainGradient linear-gradient(rgb(30, 144, 255), rgb(0, 100, 200)) contentBackgroundColor pattern(named:paper)启用方式只需设置目录ThemeManager.shared.userThemesFolderURL userThemesFolderURL更妙的是ThemeKit 用 GCD dispatch source 监视这个目录用户在运行中修改.theme文件当前主题会即时热更新无需重启应用。这对让用户调出自己的风格这类功能非常友好。七、主题感知资产颜色、渐变、图片自动切换这一节是 ThemeKit 最值得投入的地方。常规做法是写死颜色而 ThemeKit 的做法是定义资产标识让每个主题提供自己的实现。定义动态颜色在ThemeColor上添加类扩展extension ThemeColor { static var brandColor: ThemeColor { return ThemeColor.color(with: #function) } }为不同主题提供不同色值在对应主题类上添加实例扩展extension LightTheme { var brandColor: NSColor { return NSColor.orange } } extension DarkTheme { var brandColor: NSColor { return NSColor.white } }使用处完全无感知myButton.titleColor ThemeColor.brandColor切换主题时ThemeColor.brandColor自动解析为当前主题的色值。ThemeGradient和ThemeImage的使用方式完全一致ThemeGradient.gradient(with:)、ThemeImage.image(with:)渐变和图片同样可以按主题区分。此外ThemeColor是NSColor的子类你甚至可以覆盖系统命名颜色在扩展里定义static var labelColor所有使用NSColor.labelColor的控件都会自动跟随主题。如果你想做自定义绘制这非常有用。图.theme文件中pattern(named:paper)引用的纸张纹理素材位于 Demo/Demo/Assets.xcassets/paper.imageset/八、常见问题的三个补丁滚动条在深色主题下全白当用户在系统设置里选择始终显示滚动条时深色主题下滚动条可能渲染成白色。补丁方案是监听主题变化并手动改滚动条背景色scrollView?.backgroundColor ThemeColor.myBackgroundColor NotificationCenter.default.addObserver(forName: .didChangeTheme, object: nil, queue: nil) { _ in scrollView?.verticalScroller?.layer?.backgroundColor ThemeColor.myBackgroundColor.cgColor }字体平滑异常文本控件没有设置背景色时深色背景上可能出现字体渲染毛边。解决办法是始终为文本指定背景色如果是自定义绘制先set()背景色并fill再开启setShouldSmoothFonts(true)绘制文字。表格 cell 被主题化后出现背景色该问题仅影响 macOS 10.14 以下、位于 sheet 中的视图型NSTableView。可以在NSView子类里覆写背景绘制逻辑规避官方文档的 FAQ 部分有详细说明。九、下一步跑起来然后读源码与其听我讲不如直接体验。克隆仓库后打开 Demo 工程就能看到一个完整的主题切换示例左侧笔记列表、右侧控制面板、下拉菜单切换浅色/深色/自定义主题连标题栏覆盖层TitleBarOverlayView.swift都有现成实现可以参考。git clone https://gitcode.com/gh_mirrors/the/ThemeKit想深入理解实现建议按这个顺序读源码先看Sources/Theme.swift理解协议再看Sources/ThemeManager.swift弄清主题生命周期与窗口策略最后看Sources/ThemeColor.swift掌握主题感知资产的分发机制。Demo 里的LightThemeDemoSwift.swift、DarkThemeDemoSwift.swift和PaperTheme.swift则是自定义主题的最短范本。从一行ThemeManager.darkTheme.apply()到完整的用户可定制主题体系ThemeKit 只提供了很小的 API 面却覆盖了 macOS 主题化的绝大多数场景。把配色从写死的常量升级为跟随主题的资产你的应用才真正具备了换肤的能力。【免费下载链接】ThemeKitmacOS theming library项目地址: https://gitcode.com/gh_mirrors/the/ThemeKit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考