Mac Catalyst 的新特性
What's new in Mac Catalyst
2021年6月9日
一句话判断
Mac Catalyst 在 macOS 12 终于觉得自己像个原生 Mac app 了——优化键盘导航、改进工具栏、支持替换 UIKit 组件——但最关键的「不再看起来像个移植 app」这件事还需要你主动做适配。
这场 Session 讲了什么
Mac Catalyst 是 Apple 为 iPad app 提供「一键转 Mac」的方案。但在实践中,直接用 Catalyst 转出来的 app 在 Mac 上总有一种「不够原生」的感觉。macOS 12 的更新主要集中在缩小这个差距。
键盘导航方面,Tab 键现在可以在 UI 元素之间跳转,回车键可以激活按钮。这是 Mac 用户的基本预期,但之前的 Catalyst app 完全不支持。系统还会自动为 UIKit 控件添加标准的键盘快捷键(比如 Cmd+Z 撤销)。
工具栏方面,UIToolbar 在 Catalyst 中现在会自动映射为 NSToolbar,这意味着它会被放置在窗口顶部的标准位置,而不是浮动在内容区域上方。搜索栏也会自动映射到 NSSearchToolbarItem。
API 改进方面,新增了 UICommand 的 alternateAction 支持(长按展示替代菜单项),UIMenu 的键盘快捷键绑定也更加完善。
值得深挖的点
自动工具栏映射的局限性
虽然 UIToolbar 会自动映射为 NSToolbar,但映射不是完美的。自定义的 toolbar item 可能不会正确显示,特别是包含复杂自定义 view 的 item。Session 建议对于复杂的工具栏需求,用 NSToolbar 的 API 直接配置,而不是依赖 UIKit 的自动映射。
键盘快捷键的正确配置方式
Catalyst app 的键盘快捷键应该通过 UIKeyCommand 和 UIMenu 系统来配置,而不是直接监听按键事件。系统会把 UIKeyCommand 自动映射到 Mac 的菜单栏中,用户可以在菜单中看到所有可用的快捷键。
代码片段
// 配置 Catalyst 的键盘快捷键
import UIKit
class DocumentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupKeyCommands()
}
func setupKeyCommands() {
let newCommand = UIKeyCommand(
title: "新建文档",
action: #selector(newDocument),
input: "n",
modifierFlags: .command
)
let saveCommand = UIKeyCommand(
title: "保存",
action: #selector(saveDocument),
input: "s",
modifierFlags: .command
)
let undoCommand = UIKeyCommand(
title: "撤销",
action: #selector(undo),
input: "z",
modifierFlags: .command
)
// 这些快捷键会自动出现在 Mac 菜单栏中
navigationItem.leftBarButtonItems = [
UIBarButtonItem(item: newCommand),
UIBarButtonItem(item: saveCommand)
]
}
@objc func newDocument() { }
@objc func saveDocument() { }
@objc func undo() { }
}
// 使用 UIMenu 构建上下文菜单(自动支持右键)
override func viewDidLoad() {
super.viewDidLoad()
let editMenu = UIMenu(
title: "编辑",
children: [
UIAction(title: "剪切", image: UIImage(systemName: "scissors")) { _ in
self.cut()
},
UIAction(title: "复制", image: UIImage(systemName: "doc.on.doc")) { _ in
self.copy()
},
UIAction(title: "粘贴", image: UIImage(systemName: "doc.on.clipboard")) { _ in
self.paste()
}
]
)
// 在 Catalyst 中,长按或右键会触发此菜单
let interaction = UIContextMenuInteraction(delegate: self)
textView.addInteraction(interaction)
}
// 检测是否运行在 Catalyst 上,做平台特定适配
#if targetEnvironment(macCatalyst)
// Mac 特定的 UI 调整
textView.font = .systemFont(ofSize: 13) // Mac 上使用更小的字号
toolbar.isHidden = true // 使用 NSToolbar 替代
#endif
最佳实践
如果你的 Catalyst app 还没有适配键盘导航,现在就开始。Mac 用户不用键盘几乎是不可想象的。从最基本的 Tab 导航和 Cmd 快捷键开始,然后逐步加入更复杂的键盘交互(比如 Vim 风格的快捷键)。
使用 #if targetEnvironment(macCatalyst) 来做平台特定的代码,而不是运行时检测。编译时的条件判断不会影响 iOS 端的性能。
还有什么值得关注
- Catalyst app 现在可以访问
NSWindow的部分 API,比如设置窗口的titlebarAppearsTransparent。 UITraitCollection在 Catalyst 中会返回.unspecified的 userInterfaceIdiom,注意不要依赖这个值做判断。- 鼠标滚轮事件现在会被
UIScrollView正确处理,不需要额外适配。