AppKit 新特性
What's new in AppKit
2022年6月6日
一句话判断
macOS Ventura 的 AppKit 更新不算激进,但 Stage Manager 适配和 NSTableView 的性能改进是每个 Mac 开发者都需要关注的变化。
这场 Session 讲了什么
macOS Ventura 带来了一批 AppKit 改进,覆盖了窗口管理、视图性能和布局系统。最重要的变化是 Stage Manager 的引入——这个新的窗口管理方式要求应用正确处理窗口尺寸变化和 NSWindow 的 titlebar 配置。如果你的应用使用了自定义 NSWindow 子类,需要在 Stage Manager 的紧凑布局模式下验证表现。
NSTableView 和 NSOutlineView 在 Ventura 中获得了显著的性能提升。Apple 改进了大批量数据更新时的 diff 算法,NSTableView 在处理上万条数据的插入和删除时更流畅了。配合新的 NSDiffableDataSourceSnapshot API(终于从 iOS 移植过来了),数据驱动的表格视图实现起来和 UIKit 一样方便。
NSLayoutAnchor 现在支持在 Interface Builder 中直接创建约束到 layout guide 的连接,减少了手写约束的需求。NSView 的 frameLayoutGuide 和 layoutMarginsGuide 用法和 UIKit 的对应属性基本一致,跨平台开发的心智负担减轻了不少。此外,NSWindow 新增了 sheet 相关的改进,sheet 动画可以自定义 NSViewController 的 presentationAnimator。
值得深挖的点
NSTableView 的 Diffable Data Source 带来了什么
在 iOS 上用惯了 UICollectionViewDiffableDataSource 的开发者,终于在 macOS 上等到了对等 API。NSTableViewDiffableDataSource 配合 NSDiffableDataSourceSnapshot 可以完全替代 NSTableViewDataSource 的手动管理方式。好处是数据变更时只需要构建新的 snapshot 并 apply,系统自动处理动画。更关键的是,它从根源上消除了 numberOfRows(in:) 和 tableView(_:objectValueFor:) 的数据不一致 bug——这种 bug 在异步更新场景下非常常见。
Stage Manager 对窗口生命周期的影响
Stage Manager 改变了 macOS 的窗口排列逻辑。当应用窗口进入 Stage Manager 的侧边栏时,窗口会被缩放到较小尺寸。你需要确保 NSWindow 的 minSize 设置合理,否则窗口内容可能出现截断。同时,Stage Manager 下窗口的 isVisible 状态变化时机和传统模式下不同——窗口被推到侧边栏不算隐藏,只是缩小。如果你的代码依赖 windowDidBecomeKey 和 windowDidResignKey 做状态管理,需要测试在 Stage Manager 下的行为是否符合预期。
代码片段
NSTableView Diffable Data Source
// 定义数据类型
enum Section: Hashable { case main }
struct Item: Hashable {
let id: UUID
let title: String
}
// 创建 diffable data source
let dataSource = NSTableViewDiffableDataSource<Section, Item>(tableView: tableView) { tableView, column, row, item in
guard let cell = tableView.makeView(withIdentifier: column.identifier, owner: self) as? NSTableCellView else {
return nil
}
cell.textField?.stringValue = item.title
return cell
}
// 应用初始数据
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: true)
NSLayoutAnchor 使用 layoutMarginsGuide
// 使用 layoutMarginsGuide 替代硬编码边距
let contentView = view()
contentView.translatesAutoresizingMaskIntoConstraints = false
superview.addSubview(contentView)
// iOS 开发者熟悉的写法,现在 macOS 也一样
NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(
equalTo: superview.layoutMarginsGuide.leadingAnchor
),
contentView.trailingAnchor.constraint(
equalTo: superview.layoutMarginsGuide.trailingAnchor
),
contentView.topAnchor.constraint(
equalTo: superview.layoutMarginsGuide.topAnchor
)
])
Stage Manager 下处理窗口尺寸
// 在 NSWindowController 或 NSViewController 中监听尺寸变化
override func viewDidLayout() {
super.viewDidLayout()
// 当窗口很小时(Stage Manager 侧栏模式),隐藏次要内容
if view.bounds.width < 400 {
secondarySidebarView.isHidden = true
} else {
secondarySidebarView.isHidden = false
}
}
最佳实践
如果你的 macOS 应用还没有迁移到 NSTableViewDiffableDataSource,Ventura 是一个好时机。diffable data source 要求你的数据模型实现 Hashable,这迫使你重新思考数据层的结构——长远来看是好事。迁移路径是先把数据模型做成 Hashable,然后用 diffable data source 替换掉 NSTableViewDataSource 的实现。
Stage Manager 适配的关键点是测试极端窗口尺寸。把 minSize 设为内容能正常显示的最小值,不要设太大导致 Stage Manager 无法缩放你的窗口。同时检查 Auto Layout 约束在窄窗口下是否有冲突——Stage Manager 会把窗口压得很窄,之前没暴露的约束问题会集中爆发。
NSLayoutAnchor 配合 layoutMarginsGuide 是现在推荐的做法,直接用硬编码数字设置间距的方式可以逐步淘汰。如果你的应用还需要兼容 Monterey 及更早版本,这些 API 同样可用。
还有什么值得关注
NSView新增了effectiveAppearance的 KVO 支持,在深色/浅色模式切换时响应更可靠。NSToolbar的NSToolbarItem现在支持isNavigational属性,用于标记导航类按钮,Stage Manager 会据此优化 UI。NSWindow新增了subtitle属性,可以直接在标题栏显示副标题,不需要自定义 title bar。