What's new in Mac Catalyst
Developer Tools 进阶 24m

Mac Catalyst 的新特性

What's new in Mac Catalyst

2021年6月9日

在 Apple 官方观看视频

一句话判断

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 改进方面,新增了 UICommandalternateAction 支持(长按展示替代菜单项),UIMenu 的键盘快捷键绑定也更加完善。

值得深挖的点

自动工具栏映射的局限性

虽然 UIToolbar 会自动映射为 NSToolbar,但映射不是完美的。自定义的 toolbar item 可能不会正确显示,特别是包含复杂自定义 view 的 item。Session 建议对于复杂的工具栏需求,用 NSToolbar 的 API 直接配置,而不是依赖 UIKit 的自动映射。

键盘快捷键的正确配置方式

Catalyst app 的键盘快捷键应该通过 UIKeyCommandUIMenu 系统来配置,而不是直接监听按键事件。系统会把 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 正确处理,不需要额外适配。
WWDC 2021