文章

为你的 Mac App 选择一个用户界面习惯用法

在用 Mac Catalyst 构建的 Mac App 中选择 iPad 或 Mac 用户界面习惯用法。

概览

用 Mac Catalyst 构建的 Mac App 可以使用 UIUserInterfaceIdiom.padUIUserInterfaceIdiom.mac 用户界面习惯用法运行。要选择 App 运行时的习惯用法,请在 Xcode 项目中打开 Mac Catalyst,然后从以下选项中进行选择:

Scale Interface to Match iPad (缩放界面以匹配 iPad)

使用 UIUserInterfaceIdiom.pad 习惯用法运行你的 App。选择这个选项可以快速将你的 iPad App 移植到 Mac。

Optimize Interface for Mac (针对 Mac 优化界面)

使用 UIUserInterfaceIdiom.mac 习惯用法运行你的 App。选择这个选项可以显示外观和行为与 AppKit 中控件相似的控件。

首先从 iPad 习惯用法开始

默认情况下,Xcode 会在你打开 Mac Catalyst 后选择“Scale Interface to Match iPad”(缩放界面以匹配 iPad)。这个选项提供了一种将 iPad App 移植到 Mac 的简化方法。你的 Mac App 会使用 UIUserInterfaceIdiom.pad 习惯用法运行,这时 macOS 会缩放 App 的用户界面以匹配 Mac 显示环境,同时保留类似 iPad 上的外观和视图指标。

在测试你的 App 时,你可能会发现,采用外观和行为与 AppKit 中控件相似的控件,或是提供更清晰的文本,可以提升你 App 的用户体验。如果是这样,请选择“Optimize Interface for Mac”(针对 Mac 优化界面) 以更改为 UIUserInterfaceIdiom.mac。但是,选择这个选项可能需要你对 App 进行其他更改。

更新你的 App 以使用 Mac 习惯用法

选择“Optimize Interface for Mac”(针对 Mac 优化界面) 意味着你的 Mac App 会使用 UIUserInterfaceIdiom.mac 用户界面习惯用法运行,这会更改你 App 的界面。一些控件的尺寸和外观会改变,而与它们的交互体验和使用 AppKit 控件时完全相同。例如,UIButton 的外观与 NSButton 完全相同。

由于系统会在用户界面习惯用法为 UIUserInterfaceIdiom.mac 时适当地设置控件尺寸,因此不再需要缩放你 App 的界面以匹配 Mac 屏幕尺寸。屏幕点的尺寸与基于 AppKit 的 App 中完全相同。但是,如果你的 App 采用硬编码尺寸或使用尺寸适用于 iPad 的图像,你可能需要更新 App 以适应尺寸差异。你可能还需要调整自动布局约束。

一些控件提供了其他设置,可帮助你实现更像 Mac 的外观。例如,当习惯用法为 UIUserInterfaceIdiom.mac 时,通过将 preferredStyle 设置为 UISwitch.Style.checkbox,可以使 UISwitch 显示为复选框。然后,将 title 设置为复选框的文本。


let showFavoritesAtTop = UISwitch()
showFavoritesAtTop.preferredStyle = .checkbox
if traitCollection.userInterfaceIdiom == .mac {
    showFavoritesAtTop.title = "Always show favorite recipes at the top"
}

UIPageControlUIRefreshControlUIStepper 不适用于采用 Mac 习惯用法运行的 App。如果你尝试在视图中显示这些控件,你的 App 会抛出异常。当用户界面习惯用法为 UIUserInterfaceIdiom.mac 时,请用相似的功能替换这些控件。例如,用“刷新”菜单项替换 UIRefreshControl,方法是创建一个标题为“Refresh”且使用键盘快捷键 Command-R 的 UIKeyCommand 对象。然后,将这一命令添加到 App 的菜单系统中。有关更多信息,请参阅“向菜单栏和用户界面中添加菜单和快捷键”。

确定当前的用户界面习惯用法

要确定你的 App 是不是在使用 Mac 习惯用法运行,请将 userInterfaceIdiom 属性的值与 UIUserInterfaceIdiom.mac 进行比较。当比较结果为 true 时,你可以针对 Mac 量身定制 App 的行为,例如,显示一个不同的子视图。


let childViewController: UIViewController
if traitCollection.userInterfaceIdiom == .mac {
    childViewController = MacOptimizedChildViewController()
} else {
    childViewController = ChildViewController()
}
addChild(childViewController)
childViewController.view.frame = view.bounds
view.addSubview(childViewController.view)
childViewController.didMove(toParent: self)

设置首选行为风格

采用 Mac 习惯用法时,一些控件 (例如 UIButtonUISlider) 的外观与其对应 AppKit 控件完全相同。但是,在一些情况下,你可能希望在 App 中利用 Mac 习惯用法,而同时保留控件的 iPad 外观和行为。例如,让 iPad App 显示带有自定拇指图像的滑块。默认情况下,当用户界面习惯用法为 UIUserInterfaceIdiom.mac 时,用 Mac Catalyst 构建的 Mac 版 App 会显示一个标准的 macOS 滑块。

要在 App 的 iPad 和 Mac 版本中提供一致外观的滑块,请将该滑块的 preferredBehavioralStyle 设置为 UIBehavioralStyle.pad。设置这种行为风格后,滑块的行为方式就会像用户界面习惯用法是 UIUserInterfaceIdiom.pad 时一样,即使该 App 使用的是 Mac 习惯用法也是如此。

请记住,当 App 使用 Mac 习惯用法时,macOS 不会缩放 App 界面,因此,即使首选行为风格是 UIBehavioralStyle.pad,你可能也需要更新你的 App 以适应尺寸差异。例如,带有自定拇指图像的滑块在 Mac App 中与在 iPad App 中可能需要不同尺寸的图像。


let slider = UISlider()
slider.minimumValue = 0
slider.maximumValue = 1
slider.value = 0.5
slider.preferredBehavioralStyle = .pad


if slider.traitCollection.userInterfaceIdiom == .mac {
    slider.setThumbImage(#imageLiteral(resourceName: "customSliderThumbMac"), for: .normal)
} else {
    slider.setThumbImage(#imageLiteral(resourceName: "customSliderThumb"), for: .normal)
}

当行为风格为 UIBehavioralStyle.mac 时,UIButtonUISlider 的一些属性和方法在 Mac 习惯用法中不受支持,调用它们时会抛出异常;例如,为除 normal 以外的任何控件状态设置按钮的标题或图像,以及设置滑块的拇指图像、最小或最大轨迹图像、色调或值图像。但是,当控件的行为风格为 UIBehavioralStyle.pad 时,这些属性和方法可以在 Mac 习惯用法中使用。

提供不同的代码路径

即使你的 Mac App 使用 UIUserInterfaceIdiom.pad 习惯用法运行,你可能也需要更改 Mac App 的外观或行为。使用 targetEnvironment() 编译条件根据目标环境选择不同的代码路径。

例如,如果你的 iPad App 会在删除按钮旁边以弹出窗口形式显示项目删除确认,但你想在 Mac App 中以提醒形式显示该确认,则可以添加一个 targetEnvironment() 条件来确定提醒控制器的首选风格。


let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { (action) in
    if dataStore.delete(recipe) {
        self.recipe = nil
    }
}


let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)


#if targetEnvironment(macCatalyst)
let preferredStyle = UIAlertController.Style.alert
#else
let preferredStyle = UIAlertController.Style.actionSheet
#endif


let alert = UIAlertController(title: "Are you sure you want to delete \(recipe.title)?", message: nil, preferredStyle: preferredStyle)
alert.addAction(deleteAction)
alert.addAction(cancelAction)


if let popoverPresentationController = alert.popoverPresentationController {
    popoverPresentationController.barButtonItem = sender as? UIBarButtonItem
}


present(alert, animated: true, completion: nil)

另请参阅

App 支持