区块链技术博客
www.b2bchain.cn

【WWDC21 10053】优秀 Mac Catalyst App 的品质求职学习资料

本文介绍了【WWDC21 10053】优秀 Mac Catalyst App 的品质求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

作者:我就是御姐我摊牌了,iOS 开发者,SwiftGG 成员,现就职于美团,略懂一点 Flutter 和 Web。

审核:师大小海腾,iOS 开发者,iOS 摸鱼周报 编辑。就职于 Babybus,主要负责《宝宝巴士》、《宝宝巴士故事》等 App 的研发。掘金主页:师大小海腾。

    • Migrate to Mac Catalyst
      • 在开始迁移之前需要关心的
        • 检查自己的 App 是否在 iPad 和 M1 Mac 上良好运行
        • 明确自己的 App 适合使用 Mac Idiom 还是 iPad Idiom
      • 在迁移过程中需要关心的
        • 确保使用 Mac 上支持的能力
        • 关心 App 的生命周期
        • 确保 App 提供各种分辨率下的图片
        • 确保 App 布局性能足够好以丝滑适配 Window 大小变化的情况
        • 关注控件在 iPad 和 Mac 上的外观
    • Specific things you can do
      • 尽可能用 ChildView 替代 ModalPresentation 和 Popover
      • 考虑到用户不能使用触摸板或者滚动时的场景
      • 增加对 MenuBar 和快捷键的支持
      • 使用 UIWindowScene 来处理 Mac 上的多 Window 场景

Catalyst 这项技术是苹果于 2019 年的 WWDC 上推出的新技术,其目的在于让开发者能够很快的将一个 iPad App 转换成一个能够跑在 Mac 上的 App。三年来的积累也给这项技术带来了足够丰富的特性。那么,面对 Catalyst 中丰富的各种能力,如何才能让我们所创建的 App 在 Catalyst 下有更好的体验呢?在 WWDC21-10053-Qualities of a great Mac Catalyst app 中,苹果的工程师向我们指明了一些我们在使用 Catalyst 过程中需要关注的点。
整个 Session 的内容主要分为如下三个部分展开:

  • Migrate to Mac Catalyst:从整体视角上阐述了初次使用 Catalyst 时 App 会发生的变化,可以帮助作为开发者的我们从宏观角度上明确我们需要做的迁移工作
  • Specific things you can do:从一些代码细节讲解了应该如何让我们的 App 在 Mac 上能够有更好的用户体验,这部分内容是本次 Session 中最主要的部分
  • Distribution:主要提及了 Catalyst App 发布环节需要关注的一些信息,这部分内容相对较少也比较简单

那么接下来,让我们来逐一看看苹果工程师给我们的建议吧!

Migrate to Mac Catalyst

虽然是整体视角,但是在这一部分苹果的工程师仍然提及了很多的细节点,这里我们将其拆分成两个部分,分别从开始迁移之前和迁移过程中两个角度进行阐述。

在开始迁移之前需要关心的

检查自己的 App 是否在 iPad 和 M1 Mac 上良好运行

“好的开始是成功的一半”。在我们准备将我们的 App 用 Catalyst 迁移到 Mac 之前,我们最好确认一下我们的 App 是否适配了 iPad 上的这些特性:

  • Multitasking
  • UIMenuBuilder
  • Copy / Paste
  • Drag / Drop

如果我们的 App 在 iPad 上很好的适配了这些 API,那么在 Mac 上我们的 App 会自动拥有这些能力:

  • Multiple Window
  • App Menu Bar / Contextual Menu
  • Copy / Paste
  • Drag / Drop

除此之外,如果我们手边还有 M1 的 Mac,我们也可以检验一下我们的 App 是否可以直接在没有任何修改的情况下运行在 M1 的 Mac 上。对应的,WWDC21 中也有一个关于如何在 M1 上提高 App 质量的 Session,感兴趣的小伙伴可以看一看。

小 Tips:
为什么确认这两点对我们接下来的迁移工作非常重要?
我们可以发现 Multiple Window / App Menu Bar / Contextual Menu / Copy / Paste / Drag / Drop 这些特性对于一个 Mac App 来说是比较基本的能力,一些基于 Electron 的 App 可能在这些方面上就做的不够好,这可能也是我们觉得这种类型 Mac App “不好用” 的原因之一。
而能够在 M1 的 Mac 上无修改的运行,说明我们的 App 没有使用到一些在 Mac 上不存在的能力(例如 AR、陀螺仪等),这也能让我们的 App 在后续的适配过程中无需关注类似的问题。

明确自己的 App 适合使用 Mac Idiom 还是 iPad Idiom

这可能是在整个适配过程中,作为开发者的我们要做出的最重要的决定了。
当我们在 Xcode 中利用 Catalyst 将 App 的迁移到 Mac 上时,我们可以从 “Scale Interface to Match iPad”(iPad Idiom)和 “Optimize Interface for Mac”(Mac Idiom)中进行选择:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

这两种 idiom 各有优劣,不过现在我们只需要记住整体的结论:

  • iPad Idiom 适配的工作量更少,但是细节不足
  • Mac Idiom 适配的工作量更多,但可以更好的打磨 Mac App 的细节

如果想知道这两种 idiom 的各自的一些细节,好进一步做出更准确的决断,可以回顾一下 WWDC20-10056-美化 Mac Catalyst app 中的内容😃。

在迁移过程中需要关心的

确保使用 Mac 上支持的能力

在迁移过程中,我们需要注意 Xcode 给出的编译时警告和错误,同时也需要关心运行时给出的异常信息。
例如,如果迁移后编译失败,我们首先需要检查的就是是否使用了一些标记为废弃的框架,我们需要将其替换成对应的新框架:

Catalyst 上不可用的框架 应该迁移到的框架
UIWebView WKWebView
AddressBook Contacts
OpenGLES Metal

除此之外,第三方二进制库也可能会导致我们的 App 在 Catalyst 下编译失败,如果这些二进制库使用了 XCFrameworks,我们需要确保这些第三方二进制库在 XCFramework 中提供了针对 Mac 的二进制。

小 Tips:

对于框架开发者来说,让自己的二进制产物适配 Mac Catalyst 的主要工作就是让自己的二进制中增加可以在 Mac 上使用的 x86_64 架构。如果使用的是 .a 或者 .framework 的 Fat Binary 形式,需要增加针对 Mac Catalyst 的 x86_64h 架构;如果使用的是 XCFrameowork,则添加一个针对 Mac Catalyst 的变体。

而对于使用到二进制产物框架的开发者来说,如果遇到二进制产物暂时还没有适配 Mac Catalyst,可以选择将这部分内容暂时屏蔽掉(将其 Platform 设置为 iOS only,并在代码中针对 Mac Catalyst 将这些功能关闭)

更具体的方式可以参阅 iOS – 关于使用 Mac Catalyst 技术时遇到的二进制库链接问题分析及解决 这篇文章。

关心 App 的生命周期

由于 Mac App 和 iPad App 的形态上有很多区别,因此,二者在生命周期上会有很多的不同点,主要体现在这几点:

不同点 原因 解决方案
Window 的切换更依赖 SceneDelegate 而不是 AppDelegate 中的事件 AppDelegate 原有的 API 设计不足以支撑多 Window 场景的复杂情况 更多的使用 SceneDelegate 而不是 AppDelegate 中的生命周期事件
SceneDelegate 中的 sceneDidEnterBackground 触发的更少 在 Mac 上只有当用户关闭或者最小化 Window 时,sceneDidEnterBackground 才会被触发,一个 Widnow 失焦时并不会触发该事件;与之对应的,iPad 上只要 Window 不可见,该事件就会被触发 如果 App 依赖该事件来做一些类似自动保存这样的功能,在 Mac 上需要使用 timer 来定时执行,以保证该功能能够正常的生效
App 还处于前台运行状态但是没有一个 Scene 存在 在 Mac 上 App 可以在前台运行但没有一个 Window(例如当我们关闭 Safari 的所有页面,但仍然可以在 MenuBar 中看到当前运行的 App 是 Safari) 在 Mac 上不要依赖 Scene 的数量来判定当前 App 是否处于前台

确保 App 提供各种分辨率下的图片

由于在 Mac 上我们的 App 可能会在各种分辨率下运行,因此我们有必要在 xcassets 中给我们的 App 提供 Mac 下的 1x 和 2x 图:
【WWDC21 10053】优秀 Mac Catalyst App 的品质

Tips:
图中 xcassets 中的 iPad – Mac Scaled 是选择 iPad Idiom 时使用的图片,而底下的 Mac 则是使用 Mac Idiom 时会使用的图片

确保 App 布局性能足够好以丝滑适配 Window 大小变化的情况

由于在 Mac 上,用户很可能会调整 App Window 的大小,因此我们需要关注 App 的布局性能,以保证 Window 大小在调整时不会有卡顿情况的发生。

关注控件在 iPad 和 Mac 上的外观

当我们开启了 Mac Idiom 后,我们所使用的 UIControl 如果可能会以 Mac AppKit 风格的形式被展示出来。不过针对 button 和 slider,我们可以单独对其外观进行配置,以让其在 Mac 上不以 Mac 风格的控件展示(参见 WWDC21-10052-What’s new in Mac Catalyst – 支持在 Mac Idiom 下保持自定义的 button 和 slider 样式),尤其是针对 button,我们可以实现更多细节上的控制(参见 WWDC21-10052-What’s new in Mac Catalyst – 更多的按钮样式 )
另外,考虑到 App 在 Mac 上的表现,我们在编写自定义控件的时候也需要更加谨慎,因为很多系统控件在 Mac Catalyst 环境下会渲染成适合 Mac 平台风格的控件(而这样的特性是自定义控件所没有的)。例如,UISwitch 在 style 属性被设置为 checkbox 时,配合仅在 checkbox style 下生效的 title 属性,我们就可以很快速的得到一个 CheckBox 控件(相比于 Switch,CheckBox 更符合 Mac 平台的风格)。
【WWDC21 10053】优秀 Mac Catalyst App 的品质

Specific things you can do

讲解完了整体需要关注的点,我们再来看看细节上需要关注的内容。

尽可能用 ChildView 替代 ModalPresentation 和 Popover

由于在 Mac 上 App 拥有更大的展示空间,所以如果 App 使用了很多 ModalPresentation 和 Popover 的方式来承载功能,那么在 Mac 上可以考虑将其用 ChildView 的形式进行展示:
【WWDC21 10053】优秀 Mac Catalyst App 的品质

考虑到用户不能使用触摸板或者滚动时的场景

由于 Mac 上不是所有的设备都使用了触摸板(有一些鼠标甚至不支持滚动),因此 Pinch/Scroll 这些在 iPad 上非常基本的手势在 Mac 上不能得到百分百的支持。这种情况下,我们需要让我们的 App 中提供相应的按钮来完成这些手势本来要完成的功能。在这方面最典型的可能就是苹果自带的地图 App。在地图 App 中,旋转和缩放都可以仅通过鼠标来完成:
【WWDC21 10053】优秀 Mac Catalyst App 的品质
除此之外,让 Tap/Pan 手势与键盘快捷键进行组合也是一个不错的办法,例如在地图 App 中,我们可以通过按住 Shift 并在地图中摁住鼠标左键并上下拖动的方式,来快速的实现地图页面的缩放。

增加对 MenuBar 和快捷键的支持

一个好的 Mac App 应该有很多的快捷键可以供用户使用,从 iOS 13 开始,开发者可以使用 UIResponder 的 keyCommands API 来为响应者链上的某个元素增加快捷键支持,同时也可以使用 UIMenuBuilder 这个 API 为 Mac App 增加顶部的 MenuBar 上的快捷键的支持。

Tips:
快捷键的支持可以在 WWDC20-10109-Support hardware keyboards in your app 中了解具体使用方式;
UIMenuBuilder 的具体使用方式可以在 Adding Menus and Shortcuts to the Menu Bar and User Interface 中了解;

在 iOS 15 之前,iPad 上的 Shortcuts Overlay(就是长按 ⌘ 时页面出现的浮层),展示的是开发者在 UIResponder 的 keyCommands 中返回的 UIKeyCommand 对象所指定的快捷键:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

image.png

在这种情况下,UIMenuBuilder 只能用来给 Mac Catalyst App 增加 Menu Bar 的支持而不能用来在 iPad 上展示快捷键,这不得不说一个比较割裂的设计。在 iOS 15 中,这个问题得到了解决,iPad 上会按照类似 Mac 上 MenuBar 的形式来展示 Shortcuts Overlay:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

image.png

与此同时,需要注意的是,由于快捷键的实现是基于响应者链的,所以我们不应该修改整个响应者链体系(也就是说不要重写 nextResponsder)。如果确实一些功能需要不在响应者链中的对象来处理,可以使用 target(forAction:withSender:) 来实现:

final class MyView: UIView {     override func target(forAction action: Selector, withSender sender: Any?) -> Any? {         if action == #selector(Model.setAsFavorite(_:)) {             return myModel         } else {             return super.target(forAction: action, withSender: sender)         }     } }

另外,由于一些快捷键是否能够启用是和当前的 First Responder 有关的,如果我们的 App 有更多的 View 可以成为 First Responder,那么当用户选中或者聚焦在某些 View 上时,用户就能够使用更多的快捷键,所以如果能够让更多的 View 利用 canBecomeFirstResponder 和 canBecomeFocused 响应用户的操作,对 App 的整体体验也是大有裨益的。更多的细节,可以参考 WWDC21-10260-Focus on iPad keyboard navigation。

使用 UIWindowScene 来处理 Mac 上的多 Window 场景

对 Mac App 来说,同时打开多个 Window 可能是非常常见的场景,但是对于大部分的 iOS App 来说,整个 App 生命周期中只需要关注一个 Window 即可。为了能够更好的处理 Mac 上的多 Window 场景,苹果的工程师建议我们使用 UIWindowScene 来更好的处理这些 Window 之间的关联。

为了能够区分不同的 UIWindowScene,我们可以在 Info.plist 的 Application Scene Manifest 中通过给每一个 Scene 增加 Scene Configuration 来告诉系统一个 UIWindowScene 应该使用哪个类、哪个 Storyboard 以及哪个父类来初始化:
【WWDC21 10053】优秀 Mac Catalyst App 的品质
接下来,当我们需要展示一个新的 UIWindowScene 的时候,我们可以用 NSUserActivity 配合 UIApplication 的 requestSceneSessionActivation(_:userActivity:options:errorHandler:) 来向系统发起创建新 UIWindowScene 的请求 。例如,我们可以在页面被双击的时候创建一个新的 viewDetail 类型的 UIWindowScene,同时将当前被点击的 item 的 ID 通过 userInfo 传入:

// Requesting a new scene let viewDetailActivityType = "viewDetail" let itemIDKey = "itemID" final class MyView: UIView {     @objc func viewDoubleClicked(_ sender: Any?) {         let userActivity = NSUserActivity(activityType: viewDetailActivityType)         userActivity.userInfo = [itemIDKey: selectedItem.itemID]         UIApplication.shared.requestSceneSessionActivation(nil,             userActivity: userActivity,             options: nil,             errorHandler: { error in //...         })     }     //... }

以上代码在默认情况下会使用 Info.plist 中 name 为 “Default Configuration” 的 SceneConfiguration,为了能够动态根据 NSUserActivity 的类型来匹配对应的 SceneConfiguration。我们需要修改 application(_:configurationForConnecting:options:) 中的实现(此时新的 UIScene 以及对应的 UIViewController 还没有被创建出来,因此无法在 UI 上做任何修改):
“`swift
// Responding to a new scene request
let viewDetailActivityType = “viewDetail”
final class AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication,

作者:我就是御姐我摊牌了,iOS 开发者,SwiftGG 成员,现就职于美团,略懂一点 Flutter 和 Web。

审核:师大小海腾,iOS 开发者,iOS 摸鱼周报 编辑。就职于 Babybus,主要负责《宝宝巴士》、《宝宝巴士故事》等 App 的研发。掘金主页:师大小海腾。

    • Migrate to Mac Catalyst
      • 在开始迁移之前需要关心的
        • 检查自己的 App 是否在 iPad 和 M1 Mac 上良好运行
        • 明确自己的 App 适合使用 Mac Idiom 还是 iPad Idiom
      • 在迁移过程中需要关心的
        • 确保使用 Mac 上支持的能力
        • 关心 App 的生命周期
        • 确保 App 提供各种分辨率下的图片
        • 确保 App 布局性能足够好以丝滑适配 Window 大小变化的情况
        • 关注控件在 iPad 和 Mac 上的外观
    • Specific things you can do
      • 尽可能用 ChildView 替代 ModalPresentation 和 Popover
      • 考虑到用户不能使用触摸板或者滚动时的场景
      • 增加对 MenuBar 和快捷键的支持
      • 使用 UIWindowScene 来处理 Mac 上的多 Window 场景

Catalyst 这项技术是苹果于 2019 年的 WWDC 上推出的新技术,其目的在于让开发者能够很快的将一个 iPad App 转换成一个能够跑在 Mac 上的 App。三年来的积累也给这项技术带来了足够丰富的特性。那么,面对 Catalyst 中丰富的各种能力,如何才能让我们所创建的 App 在 Catalyst 下有更好的体验呢?在 WWDC21-10053-Qualities of a great Mac Catalyst app 中,苹果的工程师向我们指明了一些我们在使用 Catalyst 过程中需要关注的点。
整个 Session 的内容主要分为如下三个部分展开:

  • Migrate to Mac Catalyst:从整体视角上阐述了初次使用 Catalyst 时 App 会发生的变化,可以帮助作为开发者的我们从宏观角度上明确我们需要做的迁移工作
  • Specific things you can do:从一些代码细节讲解了应该如何让我们的 App 在 Mac 上能够有更好的用户体验,这部分内容是本次 Session 中最主要的部分
  • Distribution:主要提及了 Catalyst App 发布环节需要关注的一些信息,这部分内容相对较少也比较简单

那么接下来,让我们来逐一看看苹果工程师给我们的建议吧!

Migrate to Mac Catalyst

虽然是整体视角,但是在这一部分苹果的工程师仍然提及了很多的细节点,这里我们将其拆分成两个部分,分别从开始迁移之前和迁移过程中两个角度进行阐述。

在开始迁移之前需要关心的

检查自己的 App 是否在 iPad 和 M1 Mac 上良好运行

“好的开始是成功的一半”。在我们准备将我们的 App 用 Catalyst 迁移到 Mac 之前,我们最好确认一下我们的 App 是否适配了 iPad 上的这些特性:

  • Multitasking
  • UIMenuBuilder
  • Copy / Paste
  • Drag / Drop

如果我们的 App 在 iPad 上很好的适配了这些 API,那么在 Mac 上我们的 App 会自动拥有这些能力:

  • Multiple Window
  • App Menu Bar / Contextual Menu
  • Copy / Paste
  • Drag / Drop

除此之外,如果我们手边还有 M1 的 Mac,我们也可以检验一下我们的 App 是否可以直接在没有任何修改的情况下运行在 M1 的 Mac 上。对应的,WWDC21 中也有一个关于如何在 M1 上提高 App 质量的 Session,感兴趣的小伙伴可以看一看。

小 Tips:
为什么确认这两点对我们接下来的迁移工作非常重要?
我们可以发现 Multiple Window / App Menu Bar / Contextual Menu / Copy / Paste / Drag / Drop 这些特性对于一个 Mac App 来说是比较基本的能力,一些基于 Electron 的 App 可能在这些方面上就做的不够好,这可能也是我们觉得这种类型 Mac App “不好用” 的原因之一。
而能够在 M1 的 Mac 上无修改的运行,说明我们的 App 没有使用到一些在 Mac 上不存在的能力(例如 AR、陀螺仪等),这也能让我们的 App 在后续的适配过程中无需关注类似的问题。

明确自己的 App 适合使用 Mac Idiom 还是 iPad Idiom

这可能是在整个适配过程中,作为开发者的我们要做出的最重要的决定了。
当我们在 Xcode 中利用 Catalyst 将 App 的迁移到 Mac 上时,我们可以从 “Scale Interface to Match iPad”(iPad Idiom)和 “Optimize Interface for Mac”(Mac Idiom)中进行选择:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

这两种 idiom 各有优劣,不过现在我们只需要记住整体的结论:

  • iPad Idiom 适配的工作量更少,但是细节不足
  • Mac Idiom 适配的工作量更多,但可以更好的打磨 Mac App 的细节

如果想知道这两种 idiom 的各自的一些细节,好进一步做出更准确的决断,可以回顾一下 WWDC20-10056-美化 Mac Catalyst app 中的内容😃。

在迁移过程中需要关心的

确保使用 Mac 上支持的能力

在迁移过程中,我们需要注意 Xcode 给出的编译时警告和错误,同时也需要关心运行时给出的异常信息。
例如,如果迁移后编译失败,我们首先需要检查的就是是否使用了一些标记为废弃的框架,我们需要将其替换成对应的新框架:

Catalyst 上不可用的框架 应该迁移到的框架
UIWebView WKWebView
AddressBook Contacts
OpenGLES Metal

除此之外,第三方二进制库也可能会导致我们的 App 在 Catalyst 下编译失败,如果这些二进制库使用了 XCFrameworks,我们需要确保这些第三方二进制库在 XCFramework 中提供了针对 Mac 的二进制。

小 Tips:

对于框架开发者来说,让自己的二进制产物适配 Mac Catalyst 的主要工作就是让自己的二进制中增加可以在 Mac 上使用的 x86_64 架构。如果使用的是 .a 或者 .framework 的 Fat Binary 形式,需要增加针对 Mac Catalyst 的 x86_64h 架构;如果使用的是 XCFrameowork,则添加一个针对 Mac Catalyst 的变体。

而对于使用到二进制产物框架的开发者来说,如果遇到二进制产物暂时还没有适配 Mac Catalyst,可以选择将这部分内容暂时屏蔽掉(将其 Platform 设置为 iOS only,并在代码中针对 Mac Catalyst 将这些功能关闭)

更具体的方式可以参阅 iOS – 关于使用 Mac Catalyst 技术时遇到的二进制库链接问题分析及解决 这篇文章。

关心 App 的生命周期

由于 Mac App 和 iPad App 的形态上有很多区别,因此,二者在生命周期上会有很多的不同点,主要体现在这几点:

不同点 原因 解决方案
Window 的切换更依赖 SceneDelegate 而不是 AppDelegate 中的事件 AppDelegate 原有的 API 设计不足以支撑多 Window 场景的复杂情况 更多的使用 SceneDelegate 而不是 AppDelegate 中的生命周期事件
SceneDelegate 中的 sceneDidEnterBackground 触发的更少 在 Mac 上只有当用户关闭或者最小化 Window 时,sceneDidEnterBackground 才会被触发,一个 Widnow 失焦时并不会触发该事件;与之对应的,iPad 上只要 Window 不可见,该事件就会被触发 如果 App 依赖该事件来做一些类似自动保存这样的功能,在 Mac 上需要使用 timer 来定时执行,以保证该功能能够正常的生效
App 还处于前台运行状态但是没有一个 Scene 存在 在 Mac 上 App 可以在前台运行但没有一个 Window(例如当我们关闭 Safari 的所有页面,但仍然可以在 MenuBar 中看到当前运行的 App 是 Safari) 在 Mac 上不要依赖 Scene 的数量来判定当前 App 是否处于前台

确保 App 提供各种分辨率下的图片

由于在 Mac 上我们的 App 可能会在各种分辨率下运行,因此我们有必要在 xcassets 中给我们的 App 提供 Mac 下的 1x 和 2x 图:
【WWDC21 10053】优秀 Mac Catalyst App 的品质

Tips:
图中 xcassets 中的 iPad – Mac Scaled 是选择 iPad Idiom 时使用的图片,而底下的 Mac 则是使用 Mac Idiom 时会使用的图片

确保 App 布局性能足够好以丝滑适配 Window 大小变化的情况

由于在 Mac 上,用户很可能会调整 App Window 的大小,因此我们需要关注 App 的布局性能,以保证 Window 大小在调整时不会有卡顿情况的发生。

关注控件在 iPad 和 Mac 上的外观

当我们开启了 Mac Idiom 后,我们所使用的 UIControl 如果可能会以 Mac AppKit 风格的形式被展示出来。不过针对 button 和 slider,我们可以单独对其外观进行配置,以让其在 Mac 上不以 Mac 风格的控件展示(参见 WWDC21-10052-What’s new in Mac Catalyst – 支持在 Mac Idiom 下保持自定义的 button 和 slider 样式),尤其是针对 button,我们可以实现更多细节上的控制(参见 WWDC21-10052-What’s new in Mac Catalyst – 更多的按钮样式 )
另外,考虑到 App 在 Mac 上的表现,我们在编写自定义控件的时候也需要更加谨慎,因为很多系统控件在 Mac Catalyst 环境下会渲染成适合 Mac 平台风格的控件(而这样的特性是自定义控件所没有的)。例如,UISwitch 在 style 属性被设置为 checkbox 时,配合仅在 checkbox style 下生效的 title 属性,我们就可以很快速的得到一个 CheckBox 控件(相比于 Switch,CheckBox 更符合 Mac 平台的风格)。
【WWDC21 10053】优秀 Mac Catalyst App 的品质

Specific things you can do

讲解完了整体需要关注的点,我们再来看看细节上需要关注的内容。

尽可能用 ChildView 替代 ModalPresentation 和 Popover

由于在 Mac 上 App 拥有更大的展示空间,所以如果 App 使用了很多 ModalPresentation 和 Popover 的方式来承载功能,那么在 Mac 上可以考虑将其用 ChildView 的形式进行展示:
【WWDC21 10053】优秀 Mac Catalyst App 的品质

考虑到用户不能使用触摸板或者滚动时的场景

由于 Mac 上不是所有的设备都使用了触摸板(有一些鼠标甚至不支持滚动),因此 Pinch/Scroll 这些在 iPad 上非常基本的手势在 Mac 上不能得到百分百的支持。这种情况下,我们需要让我们的 App 中提供相应的按钮来完成这些手势本来要完成的功能。在这方面最典型的可能就是苹果自带的地图 App。在地图 App 中,旋转和缩放都可以仅通过鼠标来完成:
【WWDC21 10053】优秀 Mac Catalyst App 的品质
除此之外,让 Tap/Pan 手势与键盘快捷键进行组合也是一个不错的办法,例如在地图 App 中,我们可以通过按住 Shift 并在地图中摁住鼠标左键并上下拖动的方式,来快速的实现地图页面的缩放。

增加对 MenuBar 和快捷键的支持

一个好的 Mac App 应该有很多的快捷键可以供用户使用,从 iOS 13 开始,开发者可以使用 UIResponder 的 keyCommands API 来为响应者链上的某个元素增加快捷键支持,同时也可以使用 UIMenuBuilder 这个 API 为 Mac App 增加顶部的 MenuBar 上的快捷键的支持。

Tips:
快捷键的支持可以在 WWDC20-10109-Support hardware keyboards in your app 中了解具体使用方式;
UIMenuBuilder 的具体使用方式可以在 Adding Menus and Shortcuts to the Menu Bar and User Interface 中了解;

在 iOS 15 之前,iPad 上的 Shortcuts Overlay(就是长按 ⌘ 时页面出现的浮层),展示的是开发者在 UIResponder 的 keyCommands 中返回的 UIKeyCommand 对象所指定的快捷键:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

image.png

在这种情况下,UIMenuBuilder 只能用来给 Mac Catalyst App 增加 Menu Bar 的支持而不能用来在 iPad 上展示快捷键,这不得不说一个比较割裂的设计。在 iOS 15 中,这个问题得到了解决,iPad 上会按照类似 Mac 上 MenuBar 的形式来展示 Shortcuts Overlay:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

image.png

与此同时,需要注意的是,由于快捷键的实现是基于响应者链的,所以我们不应该修改整个响应者链体系(也就是说不要重写 nextResponsder)。如果确实一些功能需要不在响应者链中的对象来处理,可以使用 target(forAction:withSender:) 来实现:

final class MyView: UIView {     override func target(forAction action: Selector, withSender sender: Any?) -> Any? {         if action == #selector(Model.setAsFavorite(_:)) {             return myModel         } else {             return super.target(forAction: action, withSender: sender)         }     } }

另外,由于一些快捷键是否能够启用是和当前的 First Responder 有关的,如果我们的 App 有更多的 View 可以成为 First Responder,那么当用户选中或者聚焦在某些 View 上时,用户就能够使用更多的快捷键,所以如果能够让更多的 View 利用 canBecomeFirstResponder 和 canBecomeFocused 响应用户的操作,对 App 的整体体验也是大有裨益的。更多的细节,可以参考 WWDC21-10260-Focus on iPad keyboard navigation。

使用 UIWindowScene 来处理 Mac 上的多 Window 场景

对 Mac App 来说,同时打开多个 Window 可能是非常常见的场景,但是对于大部分的 iOS App 来说,整个 App 生命周期中只需要关注一个 Window 即可。为了能够更好的处理 Mac 上的多 Window 场景,苹果的工程师建议我们使用 UIWindowScene 来更好的处理这些 Window 之间的关联。

为了能够区分不同的 UIWindowScene,我们可以在 Info.plist 的 Application Scene Manifest 中通过给每一个 Scene 增加 Scene Configuration 来告诉系统一个 UIWindowScene 应该使用哪个类、哪个 Storyboard 以及哪个父类来初始化:
【WWDC21 10053】优秀 Mac Catalyst App 的品质
接下来,当我们需要展示一个新的 UIWindowScene 的时候,我们可以用 NSUserActivity 配合 UIApplication 的 requestSceneSessionActivation(_:userActivity:options:errorHandler:) 来向系统发起创建新 UIWindowScene 的请求 。例如,我们可以在页面被双击的时候创建一个新的 viewDetail 类型的 UIWindowScene,同时将当前被点击的 item 的 ID 通过 userInfo 传入:

// Requesting a new scene let viewDetailActivityType = "viewDetail" let itemIDKey = "itemID" final class MyView: UIView {     @objc func viewDoubleClicked(_ sender: Any?) {         let userActivity = NSUserActivity(activityType: viewDetailActivityType)         userActivity.userInfo = [itemIDKey: selectedItem.itemID]         UIApplication.shared.requestSceneSessionActivation(nil,             userActivity: userActivity,             options: nil,             errorHandler: { error in //...         })     }     //... }

以上代码在默认情况下会使用 Info.plist 中 name 为 “Default Configuration” 的 SceneConfiguration,为了能够动态根据 NSUserActivity 的类型来匹配对应的 SceneConfiguration。我们需要修改 application(_:configurationForConnecting:options:) 中的实现(此时新的 UIScene 以及对应的 UIViewController 还没有被创建出来,因此无法在 UI 上做任何修改):
“`swift
// Responding to a new scene request
let viewDetailActivityType = “viewDetail”
final class AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication,

作者:我就是御姐我摊牌了,iOS 开发者,SwiftGG 成员,现就职于美团,略懂一点 Flutter 和 Web。

审核:师大小海腾,iOS 开发者,iOS 摸鱼周报 编辑。就职于 Babybus,主要负责《宝宝巴士》、《宝宝巴士故事》等 App 的研发。掘金主页:师大小海腾。

    • Migrate to Mac Catalyst
      • 在开始迁移之前需要关心的
        • 检查自己的 App 是否在 iPad 和 M1 Mac 上良好运行
        • 明确自己的 App 适合使用 Mac Idiom 还是 iPad Idiom
      • 在迁移过程中需要关心的
        • 确保使用 Mac 上支持的能力
        • 关心 App 的生命周期
        • 确保 App 提供各种分辨率下的图片
        • 确保 App 布局性能足够好以丝滑适配 Window 大小变化的情况
        • 关注控件在 iPad 和 Mac 上的外观
    • Specific things you can do
      • 尽可能用 ChildView 替代 ModalPresentation 和 Popover
      • 考虑到用户不能使用触摸板或者滚动时的场景
      • 增加对 MenuBar 和快捷键的支持
      • 使用 UIWindowScene 来处理 Mac 上的多 Window 场景

Catalyst 这项技术是苹果于 2019 年的 WWDC 上推出的新技术,其目的在于让开发者能够很快的将一个 iPad App 转换成一个能够跑在 Mac 上的 App。三年来的积累也给这项技术带来了足够丰富的特性。那么,面对 Catalyst 中丰富的各种能力,如何才能让我们所创建的 App 在 Catalyst 下有更好的体验呢?在 WWDC21-10053-Qualities of a great Mac Catalyst app 中,苹果的工程师向我们指明了一些我们在使用 Catalyst 过程中需要关注的点。
整个 Session 的内容主要分为如下三个部分展开:

  • Migrate to Mac Catalyst:从整体视角上阐述了初次使用 Catalyst 时 App 会发生的变化,可以帮助作为开发者的我们从宏观角度上明确我们需要做的迁移工作
  • Specific things you can do:从一些代码细节讲解了应该如何让我们的 App 在 Mac 上能够有更好的用户体验,这部分内容是本次 Session 中最主要的部分
  • Distribution:主要提及了 Catalyst App 发布环节需要关注的一些信息,这部分内容相对较少也比较简单

那么接下来,让我们来逐一看看苹果工程师给我们的建议吧!

Migrate to Mac Catalyst

虽然是整体视角,但是在这一部分苹果的工程师仍然提及了很多的细节点,这里我们将其拆分成两个部分,分别从开始迁移之前和迁移过程中两个角度进行阐述。

在开始迁移之前需要关心的

检查自己的 App 是否在 iPad 和 M1 Mac 上良好运行

“好的开始是成功的一半”。在我们准备将我们的 App 用 Catalyst 迁移到 Mac 之前,我们最好确认一下我们的 App 是否适配了 iPad 上的这些特性:

  • Multitasking
  • UIMenuBuilder
  • Copy / Paste
  • Drag / Drop

如果我们的 App 在 iPad 上很好的适配了这些 API,那么在 Mac 上我们的 App 会自动拥有这些能力:

  • Multiple Window
  • App Menu Bar / Contextual Menu
  • Copy / Paste
  • Drag / Drop

除此之外,如果我们手边还有 M1 的 Mac,我们也可以检验一下我们的 App 是否可以直接在没有任何修改的情况下运行在 M1 的 Mac 上。对应的,WWDC21 中也有一个关于如何在 M1 上提高 App 质量的 Session,感兴趣的小伙伴可以看一看。

小 Tips:
为什么确认这两点对我们接下来的迁移工作非常重要?
我们可以发现 Multiple Window / App Menu Bar / Contextual Menu / Copy / Paste / Drag / Drop 这些特性对于一个 Mac App 来说是比较基本的能力,一些基于 Electron 的 App 可能在这些方面上就做的不够好,这可能也是我们觉得这种类型 Mac App “不好用” 的原因之一。
而能够在 M1 的 Mac 上无修改的运行,说明我们的 App 没有使用到一些在 Mac 上不存在的能力(例如 AR、陀螺仪等),这也能让我们的 App 在后续的适配过程中无需关注类似的问题。

明确自己的 App 适合使用 Mac Idiom 还是 iPad Idiom

这可能是在整个适配过程中,作为开发者的我们要做出的最重要的决定了。
当我们在 Xcode 中利用 Catalyst 将 App 的迁移到 Mac 上时,我们可以从 “Scale Interface to Match iPad”(iPad Idiom)和 “Optimize Interface for Mac”(Mac Idiom)中进行选择:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

这两种 idiom 各有优劣,不过现在我们只需要记住整体的结论:

  • iPad Idiom 适配的工作量更少,但是细节不足
  • Mac Idiom 适配的工作量更多,但可以更好的打磨 Mac App 的细节

如果想知道这两种 idiom 的各自的一些细节,好进一步做出更准确的决断,可以回顾一下 WWDC20-10056-美化 Mac Catalyst app 中的内容😃。

在迁移过程中需要关心的

确保使用 Mac 上支持的能力

在迁移过程中,我们需要注意 Xcode 给出的编译时警告和错误,同时也需要关心运行时给出的异常信息。
例如,如果迁移后编译失败,我们首先需要检查的就是是否使用了一些标记为废弃的框架,我们需要将其替换成对应的新框架:

Catalyst 上不可用的框架 应该迁移到的框架
UIWebView WKWebView
AddressBook Contacts
OpenGLES Metal

除此之外,第三方二进制库也可能会导致我们的 App 在 Catalyst 下编译失败,如果这些二进制库使用了 XCFrameworks,我们需要确保这些第三方二进制库在 XCFramework 中提供了针对 Mac 的二进制。

小 Tips:

对于框架开发者来说,让自己的二进制产物适配 Mac Catalyst 的主要工作就是让自己的二进制中增加可以在 Mac 上使用的 x86_64 架构。如果使用的是 .a 或者 .framework 的 Fat Binary 形式,需要增加针对 Mac Catalyst 的 x86_64h 架构;如果使用的是 XCFrameowork,则添加一个针对 Mac Catalyst 的变体。

而对于使用到二进制产物框架的开发者来说,如果遇到二进制产物暂时还没有适配 Mac Catalyst,可以选择将这部分内容暂时屏蔽掉(将其 Platform 设置为 iOS only,并在代码中针对 Mac Catalyst 将这些功能关闭)

更具体的方式可以参阅 iOS – 关于使用 Mac Catalyst 技术时遇到的二进制库链接问题分析及解决 这篇文章。

关心 App 的生命周期

由于 Mac App 和 iPad App 的形态上有很多区别,因此,二者在生命周期上会有很多的不同点,主要体现在这几点:

不同点 原因 解决方案
Window 的切换更依赖 SceneDelegate 而不是 AppDelegate 中的事件 AppDelegate 原有的 API 设计不足以支撑多 Window 场景的复杂情况 更多的使用 SceneDelegate 而不是 AppDelegate 中的生命周期事件
SceneDelegate 中的 sceneDidEnterBackground 触发的更少 在 Mac 上只有当用户关闭或者最小化 Window 时,sceneDidEnterBackground 才会被触发,一个 Widnow 失焦时并不会触发该事件;与之对应的,iPad 上只要 Window 不可见,该事件就会被触发 如果 App 依赖该事件来做一些类似自动保存这样的功能,在 Mac 上需要使用 timer 来定时执行,以保证该功能能够正常的生效
App 还处于前台运行状态但是没有一个 Scene 存在 在 Mac 上 App 可以在前台运行但没有一个 Window(例如当我们关闭 Safari 的所有页面,但仍然可以在 MenuBar 中看到当前运行的 App 是 Safari) 在 Mac 上不要依赖 Scene 的数量来判定当前 App 是否处于前台

确保 App 提供各种分辨率下的图片

由于在 Mac 上我们的 App 可能会在各种分辨率下运行,因此我们有必要在 xcassets 中给我们的 App 提供 Mac 下的 1x 和 2x 图:
【WWDC21 10053】优秀 Mac Catalyst App 的品质

Tips:
图中 xcassets 中的 iPad – Mac Scaled 是选择 iPad Idiom 时使用的图片,而底下的 Mac 则是使用 Mac Idiom 时会使用的图片

确保 App 布局性能足够好以丝滑适配 Window 大小变化的情况

由于在 Mac 上,用户很可能会调整 App Window 的大小,因此我们需要关注 App 的布局性能,以保证 Window 大小在调整时不会有卡顿情况的发生。

关注控件在 iPad 和 Mac 上的外观

当我们开启了 Mac Idiom 后,我们所使用的 UIControl 如果可能会以 Mac AppKit 风格的形式被展示出来。不过针对 button 和 slider,我们可以单独对其外观进行配置,以让其在 Mac 上不以 Mac 风格的控件展示(参见 WWDC21-10052-What’s new in Mac Catalyst – 支持在 Mac Idiom 下保持自定义的 button 和 slider 样式),尤其是针对 button,我们可以实现更多细节上的控制(参见 WWDC21-10052-What’s new in Mac Catalyst – 更多的按钮样式 )
另外,考虑到 App 在 Mac 上的表现,我们在编写自定义控件的时候也需要更加谨慎,因为很多系统控件在 Mac Catalyst 环境下会渲染成适合 Mac 平台风格的控件(而这样的特性是自定义控件所没有的)。例如,UISwitch 在 style 属性被设置为 checkbox 时,配合仅在 checkbox style 下生效的 title 属性,我们就可以很快速的得到一个 CheckBox 控件(相比于 Switch,CheckBox 更符合 Mac 平台的风格)。
【WWDC21 10053】优秀 Mac Catalyst App 的品质

Specific things you can do

讲解完了整体需要关注的点,我们再来看看细节上需要关注的内容。

尽可能用 ChildView 替代 ModalPresentation 和 Popover

由于在 Mac 上 App 拥有更大的展示空间,所以如果 App 使用了很多 ModalPresentation 和 Popover 的方式来承载功能,那么在 Mac 上可以考虑将其用 ChildView 的形式进行展示:
【WWDC21 10053】优秀 Mac Catalyst App 的品质

考虑到用户不能使用触摸板或者滚动时的场景

由于 Mac 上不是所有的设备都使用了触摸板(有一些鼠标甚至不支持滚动),因此 Pinch/Scroll 这些在 iPad 上非常基本的手势在 Mac 上不能得到百分百的支持。这种情况下,我们需要让我们的 App 中提供相应的按钮来完成这些手势本来要完成的功能。在这方面最典型的可能就是苹果自带的地图 App。在地图 App 中,旋转和缩放都可以仅通过鼠标来完成:
【WWDC21 10053】优秀 Mac Catalyst App 的品质
除此之外,让 Tap/Pan 手势与键盘快捷键进行组合也是一个不错的办法,例如在地图 App 中,我们可以通过按住 Shift 并在地图中摁住鼠标左键并上下拖动的方式,来快速的实现地图页面的缩放。

增加对 MenuBar 和快捷键的支持

一个好的 Mac App 应该有很多的快捷键可以供用户使用,从 iOS 13 开始,开发者可以使用 UIResponder 的 keyCommands API 来为响应者链上的某个元素增加快捷键支持,同时也可以使用 UIMenuBuilder 这个 API 为 Mac App 增加顶部的 MenuBar 上的快捷键的支持。

Tips:
快捷键的支持可以在 WWDC20-10109-Support hardware keyboards in your app 中了解具体使用方式;
UIMenuBuilder 的具体使用方式可以在 Adding Menus and Shortcuts to the Menu Bar and User Interface 中了解;

在 iOS 15 之前,iPad 上的 Shortcuts Overlay(就是长按 ⌘ 时页面出现的浮层),展示的是开发者在 UIResponder 的 keyCommands 中返回的 UIKeyCommand 对象所指定的快捷键:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

image.png

在这种情况下,UIMenuBuilder 只能用来给 Mac Catalyst App 增加 Menu Bar 的支持而不能用来在 iPad 上展示快捷键,这不得不说一个比较割裂的设计。在 iOS 15 中,这个问题得到了解决,iPad 上会按照类似 Mac 上 MenuBar 的形式来展示 Shortcuts Overlay:

【WWDC21 10053】优秀 Mac Catalyst App 的品质

image.png

与此同时,需要注意的是,由于快捷键的实现是基于响应者链的,所以我们不应该修改整个响应者链体系(也就是说不要重写 nextResponsder)。如果确实一些功能需要不在响应者链中的对象来处理,可以使用 target(forAction:withSender:) 来实现:

final class MyView: UIView {     override func target(forAction action: Selector, withSender sender: Any?) -> Any? {         if action == #selector(Model.setAsFavorite(_:)) {             return myModel         } else {             return super.target(forAction: action, withSender: sender)         }     } }

另外,由于一些快捷键是否能够启用是和当前的 First Responder 有关的,如果我们的 App 有更多的 View 可以成为 First Responder,那么当用户选中或者聚焦在某些 View 上时,用户就能够使用更多的快捷键,所以如果能够让更多的 View 利用 canBecomeFirstResponder 和 canBecomeFocused 响应用户的操作,对 App 的整体体验也是大有裨益的。更多的细节,可以参考 WWDC21-10260-Focus on iPad keyboard navigation。

使用 UIWindowScene 来处理 Mac 上的多 Window 场景

对 Mac App 来说,同时打开多个 Window 可能是非常常见的场景,但是对于大部分的 iOS App 来说,整个 App 生命周期中只需要关注一个 Window 即可。为了能够更好的处理 Mac 上的多 Window 场景,苹果的工程师建议我们使用 UIWindowScene 来更好的处理这些 Window 之间的关联。

为了能够区分不同的 UIWindowScene,我们可以在 Info.plist 的 Application Scene Manifest 中通过给每一个 Scene 增加 Scene Configuration 来告诉系统一个 UIWindowScene 应该使用哪个类、哪个 Storyboard 以及哪个父类来初始化:
【WWDC21 10053】优秀 Mac Catalyst App 的品质
接下来,当我们需要展示一个新的 UIWindowScene 的时候,我们可以用 NSUserActivity 配合 UIApplication 的 requestSceneSessionActivation(_:userActivity:options:errorHandler:) 来向系统发起创建新 UIWindowScene 的请求 。例如,我们可以在页面被双击的时候创建一个新的 viewDetail 类型的 UIWindowScene,同时将当前被点击的 item 的 ID 通过 userInfo 传入:

// Requesting a new scene let viewDetailActivityType = "viewDetail" let itemIDKey = "itemID" final class MyView: UIView {     @objc func viewDoubleClicked(_ sender: Any?) {         let userActivity = NSUserActivity(activityType: viewDetailActivityType)         userActivity.userInfo = [itemIDKey: selectedItem.itemID]         UIApplication.shared.requestSceneSessionActivation(nil,             userActivity: userActivity,             options: nil,             errorHandler: { error in //...         })     }     //... }

以上代码在默认情况下会使用 Info.plist 中 name 为 “Default Configuration” 的 SceneConfiguration,为了能够动态根据 NSUserActivity 的类型来匹配对应的 SceneConfiguration。我们需要修改 application(_:configurationForConnecting:options:) 中的实现(此时新的 UIScene 以及对应的 UIViewController 还没有被创建出来,因此无法在 UI 上做任何修改):
“`swift
// Responding to a new scene request
let viewDetailActivityType = “viewDetail”
final class AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication,

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 【WWDC21 10053】优秀 Mac Catalyst App 的品质求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们