Optimize the interface of your Mac Catalyst app
Swift & UI 进阶 26m

优化 Mac Catalyst App 的界面

Optimize the interface of your Mac Catalyst app

2020年6月25日

在 Apple 官方观看视频

一句话判断

如果你的 Catalyst App 看起来像个”放大版的 iPad App”,这场 Session 提供了具体的优化路径——从鼠标悬停到键盘快捷键到窗口尺寸适配。

这场 Session 讲了什么

Mac Catalyst 让 iPad App 可以运行在 macOS 上,但默认状态下界面有很多”iOS 痕迹”。Session 讲解了如何让 Catalyst App 看起来更像原生 Mac App。

核心内容包括:UIHoverGestureRecognizer 处理鼠标悬停效果(Mac 用户习惯鼠标划过按钮时有视觉反馈)、UIKeyCommandUIKeyModifierFlags 实现键盘快捷键、窗口尺寸的灵活适配(Mac 窗口可以自由调整大小,不像 iPad 固定比例)。

Session 还介绍了 Catalyst App 的工具栏优化——使用 NSToolbar(通过桥接)替代 iOS 风格的导航栏工具栏,以及如何正确处理侧边栏在窗口大小变化时的折叠/展开。

值得深挖的点

鼠标悬停是 Mac 和 iPad 的核心交互差异。 iPad 上没有 hover 状态,但 Mac 用户期望按钮、列表项等在鼠标划过时有高亮效果。UIHoverGestureRecognizer 是填补这个差距的关键——但它只在 macOS 上触发,iPad 上会自动忽略。

键盘快捷键的系统性设计。 Mac App 必须支持 Cmd+N(新建)、Cmd+S(保存)、Cmd+W(关闭窗口)等标准快捷键。Catalyst App 需要通过 UIKeyCommand 逐一注册这些快捷键。Session 建议创建一个专门的 ShortcutManager 来集中管理所有快捷键映射。

代码片段

import UIKit

// 鼠标悬停效果
class HoverButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupHover()
    }
    
    private func setupHover() {
        let hoverGesture = UIHoverGestureRecognizer(
            target: self,
            action: #selector(handleHover(_:))
        )
        addGestureRecognizer(hoverGesture)
    }
    
    @objc private func handleHover(_ gesture: UIHoverGestureRecognizer) {
        switch gesture.state {
        case .began, .changed:
            // 鼠标进入按钮区域
            backgroundColor = .systemFill
            layer.shadowOpacity = 0.2
        case .ended:
            // 鼠标离开
            backgroundColor = .systemBackground
            layer.shadowOpacity = 0
        default:
            break
        }
    }
}
// 键盘快捷键
class DocumentViewController: UIViewController {
    override var keyCommands: [UIKeyCommand]? {
        return [
            UIKeyCommand(
                title: "新建文档",
                action: #selector(newDocument),
                input: "n",
                modifierFlags: .command
            ),
            UIKeyCommand(
                title: "保存",
                action: #selector(saveDocument),
                input: "s",
                modifierFlags: .command
            ),
            UIKeyCommand(
                title: "关闭窗口",
                action: #selector(closeWindow),
                input: "w",
                modifierFlags: .command
            ),
            UIKeyCommand(
                title: "撤销",
                action: #selector(undo),
                input: "z",
                modifierFlags: .command
            )
        ]
    }
    
    @objc func newDocument() { /* ... */ }
    @objc func saveDocument() { /* ... */ }
    @objc func closeWindow() { /* ... */ }
    @objc func undo() { /* ... */ }
}
// 窗口尺寸适配
class AdaptiveSplitViewController: UISplitViewController {
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        
        // Mac 窗口可以很窄,需要处理侧边栏的自动折叠
        if size.width < 700 {
            // 窄窗口:隐藏侧边栏
            preferredDisplayMode = .secondaryOnly
        } else {
            // 宽窗口:显示侧边栏
            preferredDisplayMode = .oneBesideSecondary
        }
    }
}

最佳实践

  • 为所有可交互元素添加 hover 效果。 Mac 用户期望按钮、列表项、工具栏图标在鼠标悬停时有视觉反馈。
  • 注册标准的 Cmd 快捷键。 至少支持 Cmd+N/S/W/Z/Q,覆盖新建、保存、关闭、撤销、退出。
  • 测试各种窗口尺寸。 从最小窗口到全屏,确保布局不会崩溃或出现奇怪的空白。
  • 使用 NSToolbar 替代 iOS 风格的导航栏。 Mac 用户期望窗口顶部有原生风格的工具栏。

还有什么值得关注

  • macOS Big Sur 的 Catalyst App 可以使用新的 Sidebar 样式
  • UITrackingLayoutGuide 可以帮助实现窗口大小变化的自动布局适配
  • Catalyst App 可以通过 Plugin 桥接使用部分 AppKit 控件
  • 右键菜单(Context Menu)在 Mac 上更常用,确保所有列表项都支持
WWDC 2020