Highlight
macOS Ventura 为 AppKit 应用加入 Stage Manager 窗口协作规则、Settings 风格表单、
NSComboButton、NSColorWell新样式、工具栏自定义 API、SF Symbols 4 可变符号,以及新的分享与协作入口。
核心内容
macOS Ventura 改了很多 Mac 应用的日常细节。
这些细节看起来分散:窗口、设置页、按钮、颜色选择、工具栏、表格、符号、分享。它们背后是同一个问题:Mac 应用需要更好地适应系统级工作流。
Stage Manager 会把不活跃的窗口收起来,让当前窗口留在中央。文档窗口适合这种行为。面板、弹窗、Settings、浮动工具则不应该挤走当前窗口。AppKit 现在会根据窗口类型和 collectionBehavior 帮 Stage Manager 判断窗口角色。
Settings 也变了。系统应用从 System Preferences 改名为 System Settings,控制密集的设置界面采用新的表单视觉样式。AppKit 会自动把应用菜单里的 “Preferences” 更新为 “Settings”,但窗口标题、说明文字和本地化字符串仍然要开发者自己检查。
控件层面,NSComboButton 把主操作和下拉菜单放进同一个控件。NSColorWell 获得新的外观、minimal 与 expanded 两种样式。NSToolbar 增加了更细的自定义约束。NSTableView 对可变行高表格改用懒计算,超大表格首次加载更快。
最后,SF Symbols 4 增加 450 多个符号和可变符号。分享面板也升级为更丰富的 popover,并提供预览元数据、标准菜单项和协作邀请入口。
详细内容
Stage Manager 需要窗口声明自己的角色
(01:00)Stage Manager 会把非活跃窗口移出工作区,让活跃窗口位于中央。演讲明确区分了两类窗口:文档一类的 primary window 应该进入这种交换模型;panel、popover、settings、modal window 等辅助窗口应该留在当前窗口之上。
AppKit 已经有 NSWindow API 描述这些行为。Stage Manager 会尊重窗口的 collectionBehavior。如果包含 .auxiliary、.moveToActiveSpace、.stationary 或 .transient,窗口不会把中央的活动窗口挤走。
// 概念代码:transcript 明确列出这些 collectionBehavior 选项。
let panel: NSWindow = makeInspectorPanel()
panel.collectionBehavior.insert(.auxiliary)
panel.collectionBehavior.insert(.moveToActiveSpace)
关键点:
panel代表检查器、工具面板这类辅助窗口。.auxiliary告诉系统这个窗口服务于当前工作流,角色不同于主文档窗口。.moveToActiveSpace让窗口跟随当前 Space,避免用户切换工作区后丢失上下文。- Stage Manager、Space、全屏都会读取同一组
collectionBehavior信息。
这部分的实践很直接:不要把所有窗口都当作文档窗口。Settings、面板、临时工具和弹窗应该用合适的窗口类型或 collection behavior 表达意图。
Settings 风格表单适合增量采用 SwiftUI
(02:42)System Preferences 在 Ventura 改名为 System Settings。AppKit 会在使用新 SDK 构建后,自动把应用菜单里的 “Preferences” 改为 “Settings”。演讲同时提醒,窗口标题、描述标签和其他控件里的本地化文字要自己搜索和更新。
(04:23)新的 Settings 界面使用控制密集的表单样式。SwiftUI 的 Form 可以直接采用这种视觉语言,适合从独立的 Settings 窗口开始增量引入 SwiftUI。
enum AirDropVisbility: String, CaseIterable, Identifiable {
case nobody = "No One"
case contactsOnly = "Contacts Only"
case everyone = "Everyone"
var id: String { rawValue }
var label: String { rawValue }
var symbolName: String {
switch self {
case .nobody: return "person.crop.circle.badge.xmark"
case .contactsOnly: return "person.2.circle"
case .everyone: return "person.crop.circle.badge.checkmark"
}
}
}
struct ExampleFormView: View {
@State private var name: String = "Mac Studio"
@State private var screenSharingEnabled: Bool = true
@State private var fileSharingEnabled: Bool = false
@State private var airdropVisibility = AirDropVisbility.contactsOnly
var body: some View {
Form {
TextField("Computer Name", text: $name)
Toggle("Screen Sharing", isOn: $screenSharingEnabled)
Toggle("File Sharing", isOn: $fileSharingEnabled)
Picker("AirDrop", selection: $airdropVisibility) {
ForEach(AirDropVisbility.allCases) {
Label($0.label, systemImage: $0.symbolName)
.labelStyle(.titleAndIcon)
.tag($0)
}
}
}
.formStyle(.grouped)
}
}
关键点:
AirDropVisbility用枚举定义 Settings 中的选项,并提供 label 与 SF Symbol 名称。@State保存表单状态,适合 Settings 窗口中立即可编辑的选项。Form放入TextField、Toggle和Picker,让界面结构跟设置项一致。Label(..., systemImage:)给 Picker 选项加上系统符号。.labelStyle(.titleAndIcon)同时显示文字与图标。.formStyle(.grouped)应用演讲中的 grouped form style,让视觉、滚动和布局由 SwiftUI 处理。
如果应用已经有 AppKit Settings 窗口,可以先把一组独立设置做成 SwiftUI Form,再嵌入现有窗口。演讲把 Settings 窗口称为采用 SwiftUI 的好入口,因为它通常是应用界面中的独立区域。
新控件减少自制组合控件
(05:37)AppKit 在 Ventura 增加 NSComboButton。它把一次点击的主操作和额外选项菜单合并到同一个控件中。Mail 里的“移动到预测文件夹”就是典型场景:默认位置一键可达,其他文件夹仍然可以从菜单选择。
NSComboButton 有两种样式。split 样式有独立的箭头区域,专门打开菜单。unified 样式看起来更像普通按钮,点击执行主操作,长按显示菜单。
// 概念代码:transcript 只说明控件角色与两种样式。
let archiveButton = NSComboButton()
archiveButton.menu = archiveMenu
archiveButton.style = .split
archiveButton.target = self
archiveButton.action = #selector(archiveToSuggestedMailbox(_:))
关键点:
NSComboButton用一个控件表达“默认动作 + 更多选项”。menu承载额外目标,例如移动到其他邮箱或文件夹。.split对应演讲中的默认样式,箭头区域只负责菜单。target与action保留普通按钮的一次点击主操作。
(07:21)NSColorWell 的默认外观自动更新,不需要迁移代码。它还增加 minimal 和 expanded 两种样式。minimal 样式在 rollover 时显示 disclosure arrow,并弹出快速调色板。expanded 样式把快速选择和完整 NSColorPanel 放在同一个控件里。
// 概念代码:transcript 明确列出 colorWellStyle 的三个 case。
let colorWell = NSColorWell()
colorWell.colorWellStyle = .minimal
关键点:
NSColorWell默认外观会自动现代化。.minimal适合工具栏或检查器里占位较小的颜色入口。.expanded适合 iWork 式的明确入口:左侧快速选择,右侧打开完整面板。- pulldown target/action 可以接管下拉区域,用自定义 popover 或菜单替代系统调色板。
工具栏自定义可以写出规则
(09:40)NSToolbar 的新 API 解决两个常见问题:有些项目不能被用户移走;有些项目只能出现在工具栏的某些区域。
演讲列出两个 delegate 方法。toolbarImmovableItemIdentifiers 返回不能移动或删除的 item 标识。toolbar(_:itemIdentifier:canBeInsertedAt:) 可以对重排、插入、移除进行判断。
// 概念代码:方法名来自 transcript,用于展示规则位置。
func toolbarImmovableItemIdentifiers(_ toolbar: NSToolbar) -> Set<NSToolbarItem.Identifier> {
[filterItemIdentifier]
}
func toolbar(
_ toolbar: NSToolbar,
itemIdentifier: NSToolbarItem.Identifier,
canBeInsertedAt index: Int
) -> Bool {
allowedSection.contains(index)
}
关键点:
toolbarImmovableItemIdentifiers适合 Mail 过滤按钮这类必须停在固定位置的项目。- 返回的 identifier 在自定义模式里不会被移动或删除。
canBeInsertedAt让应用按位置判断插入是否合法。allowedSection.contains(index)表达“只能放在某一段”的规则。
(10:56)centeredItemIdentifiers 支持多个居中项目。工具栏可自定义时,这些项目可以增删,但只能在居中组内重排。NSToolbarItem.possibleLabels 用于可变标题。工具栏会按最长标题计算宽度,避免状态切换时其他项目左右跳动。
// 概念代码:属性名来自 transcript。
toolbar.centeredItemIdentifiers = [cropIdentifier, adjustIdentifier, filterIdentifier]
muteItem.possibleLabels = ["Mute", "Unmute"]
关键点:
centeredItemIdentifiers把一组工具固定在工具栏中央区域。- 居中组内部仍然可以按用户设置重排。
possibleLabels预先告诉工具栏同一个 item 会出现哪些本地化标题。- 工具栏按最长标题预留宽度,状态切换时布局保持稳定。
可变行高表格不再急着测量所有行
(13:43)NSTableView 过去为了滚动体验,会提前计算所有行高。可变行高表格在数据量很大时,首次加载会被这些计算拖慢。
Ventura 改成懒计算。NSTableView 只测量当前 viewport 附近的行。未测量行使用已测量行得出的运行估算高度。滚动时,表格再请求真实行高,并维持正确的滚动位置。
// 概念代码:delegate 回调仍然存在,但调用时机在 Ventura 中改变。
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
heightCache[row] ?? estimatedHeight(for: row)
}
关键点:
heightOfRow仍然是提供行高的位置。- Ventura 会按需要请求行高,不再保证启动时一次性请求全部行。
- 代码不要依赖 delegate 调用的具体时机。
- 优化自动应用于
NSTableView和 SwiftUIList,不需要应用 opt in。
SF Symbols 4 增加 preferred rendering mode 和 variable symbols
(15:32)SF Symbols 4 在 Ventura 增加 450 多个新符号。符号渲染模式仍然包括 monochrome、hierarchical、palette 和 multicolor。新增的 preferred rendering mode 允许符号声明自己更适合的默认渲染方式。运行时 AppKit 会自动使用这个偏好。
(17:28)Variable symbol 用一个 0 到 1 之间的浮点值表达数量。符号内部有阈值,路径会根据传入值变化。演讲举了 Wi‑Fi 信号强度和音量的例子。
// 概念代码:transcript 说明新增 initializer 比现有 symbol initializer 多 value 参数。
let signalLevel = 0.75
let image = NSImage(
systemSymbolName: "wifi",
variableValue: signalLevel,
accessibilityDescription: "Wi-Fi signal"
)
关键点:
systemSymbolName选择 SF Symbol。variableValue是 0 到 1 的浮点数。- 符号如果没有定义 variable thresholds,这个值会被忽略。
- 符号如果定义了 thresholds,路径会按数值变化。
- variable symbol 可以和 palette、multicolor 等渲染方式配合。
分享入口升级为 popover,并支持协作邀请
(18:50)Ventura 的分享体验从旧的 share menu 升级为信息更丰富的 sharing popover。它能展示文档信息和 suggested people,同时保留原来 NSSharingServicePicker 的 API 与 delegate 方法。
如果分享的是 file URL,NSSharingServicePicker 可以自动填充图标、名称和文件元数据。如果分享的是自定义类型,可以让 item 遵循 NSPreviewRepresentableActivityItem,提供底层分享对象、标题、图片 provider 和图标 provider。AppKit 还提供 NSPreviewRepresentingActivityItem 作为便利类。
// 概念代码:API 名称来自 transcript,用于展示自定义分享 item 的组成。
let previewItem = NSPreviewRepresentingActivityItem(
item: itemProvider,
title: documentTitle,
image: previewImage,
icon: documentIcon
)
let picker = NSSharingServicePicker(items: [previewItem])
关键点:
NSPreviewRepresentingActivityItem把分享对象和预览元数据放在一起。itemProvider是真正要分享的内容。title、image、icon用于 sharing popover 的头部展示。- 图片生成成本较高时,演讲建议用
NSItemProvider延迟提供。
(20:44)如果分享入口来自菜单,NSSharingServicePicker 可以创建 standardShareMenuItem。把它加入主菜单或上下文菜单后,用户选择菜单项会打开标准 sharing popover。上下文菜单场景中,popover 会锚定到产生菜单的 view。
// 概念代码:属性名来自 transcript。
let shareItem = picker.standardShareMenuItem
contextMenu.addItem(shareItem)
关键点:
standardShareMenuItem避免手工枚举 sharing services。- 菜单入口仍然进入系统标准 sharing popover。
- 应用可以继续使用 picker 的过滤和自定义服务能力。
- 协作型应用可以继续研究 CloudKit、iCloud Drive 或自有服务器接入的邀请流程。
核心启发
1. 做一个 Stage Manager 适配清单
- 做什么:遍历应用里的文档窗口、Settings 窗口、检查器、浮动面板和弹窗,给每类窗口写明角色。
- 为什么值得做:Stage Manager 会根据窗口类型和
collectionBehavior决定是否把现有窗口移出中央区域。 - 怎么开始:检查辅助窗口是否应该使用
.auxiliary、.moveToActiveSpace、.stationary或.transient。
2. 把 Settings 的一个页面迁移到 SwiftUI Form
- 做什么:选择一组独立设置,用 SwiftUI
Form重写,并应用.formStyle(.grouped)。 - 为什么值得做:Settings 窗口通常边界清晰,适合在 AppKit 应用中增量引入 SwiftUI。
- 怎么开始:从只有
TextField、Toggle、Picker的页面开始,保留原来的 AppKit 窗口容器。
3. 用 NSComboButton 替换双按钮或分段控件
- 做什么:把“默认动作 + 更多目标”的控件合并成一个
NSComboButton。 - 为什么值得做:演讲明确提到过去常用 segmented control 拼出这种交互,现在有专用控件。
- 怎么开始:找出工具栏里相邻的主按钮和下拉按钮,主动作放在 button action,额外选择放进 menu。
4. 给工具栏自定义增加规则
- 做什么:限制关键 toolbar item 的移动、删除和可插入位置。
- 为什么值得做:Ventura 的
NSToolbardelegate 新增了不可移动项目和插入位置判断。 - 怎么开始:实现
toolbarImmovableItemIdentifiers,再用canBeInsertedAt限制只能出现在特定区域的 item。
5. 给分享对象补齐预览元数据
- 做什么:让自定义分享内容在 sharing popover 中显示标题、预览图和图标。
- 为什么值得做:Ventura 的分享 popover 会展示更多文档信息,并支持 suggested people 与协作入口。
- 怎么开始:对自定义分享对象使用
NSPreviewRepresentableActivityItem或NSPreviewRepresentingActivityItem,菜单入口使用standardShareMenuItem。
关联 Session
- Adopt Variable Color in SF Symbols — 继续讲 SF Symbols 4 的 variable color,适合接在本 session 的 SF Symbols 更新后阅读。
- Adopt desktop-class editing interactions — 讲桌面级编辑交互,和 AppKit 控件、菜单、键盘工作流相关。
- Bring multiple windows to your SwiftUI app — 讲 SwiftUI 多窗口,和 Stage Manager、Mac 窗口协作场景相关。
- Build a desktop-class iPad app — 讲 iPad 桌面级能力,和 Ventura 中跨平台 Settings、工具栏、窗口心智相邻。
评论
GitHub Issues · utterances