Highlight
macOS Monterey 为 AppKit 带来五大更新:控件自定义着色、SF Symbols 3 分层渲染、TextKit 2 非线性排版引擎、Swift 5.5 并发特性深度集成、以及 Shortcuts 自动化支持。
核心内容
控件着色与按钮设计更新
macOS Monterey 允许为单个按钮、分段控件和滑块设置自定义着色,通过 bezelColor、selectedSegmentColor 和 trackFillColor 实现。这在 Touch Bar 上已可用,现在扩展到窗口内控件。
一个关键设计变化:Push Button 点击时不再使用强调色高亮,而是与其他可点击元素保持一致。自定义绘制时需要使用 interiorBackgroundStyle 判断底层样式。
SF Symbols 3 的分层渲染
SF Symbols 3 引入两种新渲染模式:
- Hierarchical:单一颜色,通过不透明度区分层次
- Palette:为每一层单独指定颜色
还支持符号变体映射,如从空心 heart 自动获取填充版本。
TextKit 2 的非线性布局
TextKit 2 采用非线性布局系统,大文档只需排版可见区域附近的段落,避免从头到尾线性计算。TextEdit 的纯文本文档和 AppKit 文本字段已在 Big Sur 中悄悄使用 TextKit 2。
Swift 并发集成
AppKit 将 NSResponder、NSView、NSViewController 等核心类标记为 @MainActor,编译器层面保证 UI 操作在主线程执行。NSColorSampler 等异步 API 提供 async/await 版本。
详细内容
控件着色与背景样式判断(04:18)
// 为按钮设置语义化颜色
let preorderButton = NSButton(title: "Preorder", target: self, action: #selector(preorder))
preorderButton.bezelColor = NSColor.systemOrange
// 为分段控件设置选中颜色
let segmentControl = NSSegmentedControl()
segmentControl.selectedSegmentColor = NSColor.systemGreen
// 为滑块设置轨道填充色
let slider = NSSlider()
slider.trackFillColor = NSColor.systemBlue
自定义绘制时判断背景样式:
class CustomButtonCell: NSButtonCell {
override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
// interiorBackgroundStyle 反映按钮 bezel 的底层样式
// .normal — 无色状态(普通按钮)
// .emphasized — 彩色/强调状态(着色按钮、默认按钮、toggle 开启状态)
if interiorBackgroundStyle == .emphasized {
// 在彩色背景上绘制白色内容
drawWhiteContent()
} else {
// 在无色背景上绘制深色内容
drawDarkContent()
}
}
}
关键点:
- 着色按钮在所有激活状态都显示着色,与普通按钮的白色/灰色不同
- 避免着色按钮与默认按钮(Default Button)混淆,两者都有彩色外观
- 必须用标签或图标辅助传达控件用途,不能仅靠颜色
interiorBackgroundStyle替代检查highlight状态来判断绘制策略
颜色拾取器(14:40)
@IBAction func pickColor(_ sender: Any?) {
Task {
guard let color = await NSColorSampler().sample() else { return }
textField.textColor = color
}
}
关键点:
NSColorSampler().sample()是 async 方法,用户从屏幕任意位置选取颜色- 调用后线程让出,等待用户完成选择后继续执行
- 可以直接用在
guard let中,代码比 completion handler 更自然
菜单快捷键自动本地化
macOS Monterey 自动处理菜单快捷键的本地化:
// 自动映射不可用的快捷键
// Command-backslash 在日文键盘上没有反斜杠键
// 系统会自动映射为等效的、可输入的快捷键
// 方向性快捷键在 RTL 语言中自动镜像
// Command-[ 在 RTL 语言中自动变为 Command-]
// 对于绝对方向的操作(如左对齐),禁用镜像
let alignLeftItem = NSMenuItem(title: "Align Left", action: #selector(alignLeft), keyEquivalent: "[")
alignLeftItem.allowsAutomaticKeyEquivalentMirroring = false
// 完全禁用某个菜单项的本地化
let customItem = NSMenuItem(title: "Custom", action: #selector(custom), keyEquivalent: "k")
customItem.allowsAutomaticKeyEquivalentLocalization = false
// 整个应用禁用自动本地化
func applicationShouldAutomaticallyLocalizeKeyEquivalents(_ application: NSApplication) -> Bool {
return false
}
关键点:
- 大多数应用不需要手动干预,系统自动处理
- 仅在快捷键有绝对方向含义时禁用镜像(如 Align Left)
- 应用级禁用是最后手段,优先使用菜单项级控制
Swift 属性包装器自动刷新视图
class ConfigurableView: NSView {
// 属性变化时自动调用 setNeedsDisplay
@Invalidating(.display) var foregroundColor: NSColor = .label
// 属性变化时自动调用 setNeedsLayout
@Invalidating(.layout) var spacing: CGFloat = 8
// 属性变化时自动调用 setNeedsUpdateConstraints
@Invalidating(.constraints) var maxWidth: CGFloat = 200
// 属性变化时自动调用 invalidateIntrinsicContentSize
@Invalidating(.intrinsicContentSize) var title: String = ""
// 组合多个无效化操作
@Invalidating(.display, .layout) var cornerRadius: CGFloat = 0
}
关键点:
@Invalidating是NSView的嵌套属性包装器- 属性类型必须遵循
Equatable,包装器只在值真正变化时才执行无效化 - 内置支持
.display、.layout、.constraints、.intrinsicContentSize、.restorableState - 可通过
NSViewInvalidating协议定义自定义无效化行为
Shortcuts 集成
AppKit 应用通过 Services 支持 Shortcuts:
// 实现 validRequestor 方法,声明可接受/提供的数据类型
extension MyView: NSServicesMenuRequestor {
override func validRequestor(forSendType sendType: NSPasteboard.PasteboardType?,
returnType: NSPasteboard.PasteboardType?) -> Any? {
if returnType == .string {
return self
}
return super.validRequestor(forSendType: sendType, returnType: returnType)
}
func readSelection(from pboard: NSPasteboard) -> Bool {
// 从 pasteboard 读取数据
return true
}
func writeSelection(to pboard: NSPasteboard, types: [String]) -> Bool {
// 向 pasteboard 写入数据
return true
}
}
关键点:
- 如果应用已支持 Services,则已自动支持 Shortcuts
- Shortcuts 出现在 Services 菜单所在的所有位置
- AppKit 通过响应者链检查每个 responder 能否提供/接受指定类型的数据
核心启发
-
做什么:为 Mac 版设计工具添加屏幕取色功能
- 为什么值得做:
NSColorSampler的 async/await API 只需一行代码即可实现专业取色器 - 怎么开始:在取色按钮的 action 中调用
await NSColorSampler().sample(),获取颜色后应用到选中元素
- 为什么值得做:
-
做什么:在 Mac 版文件管理器中用 SF Symbols 3 的 Palette 模式创建彩色标签图标
- 为什么值得做:Palette 模式可以为符号的不同层指定不同颜色,标签图标可以背景层灰色、前景层彩色
- 怎么开始:使用
NSImage.SymbolConfiguration(paletteColors: [backgroundColor, foregroundColor])配置符号渲染
-
做什么:为 Mac 版文本编辑器迁移到 TextKit 2 以支持超大文档
- 为什么值得做:TextKit 2 的非线性布局在大文档上性能显著提升,只排版可见区域
- 怎么开始:创建
NSTextLayoutManager替代NSLayoutManager,使用NSTextContentStorage管理文本内容
-
做什么:在 AppKit 应用中用
@Invalidating简化自定义视图属性- 为什么值得做:消除大量
didSet中的setNeedsDisplay/setNeedsLayout样板代码 - 怎么开始:将自定义视图的配置属性加上
@Invalidating(.display)或@Invalidating(.layout)注解
- 为什么值得做:消除大量
关联 Session
- What’s new in Mac Catalyst — Mac Catalyst 新 API,与 AppKit 互补
- Qualities of a great Mac Catalyst app — 优秀 Mac 应用的品质
- Meet TextKit 2 — TextKit 2 深度介绍
评论
GitHub Issues · utterances