A6095cf086c7deade1de5787326d9f9e
WWDC20 - What's new in SwiftUI

WWDC 2020 Session 10041 - What's new in SwiftUI

本文知识目录

对于 iOS 开发而言, WWDC 2019 中推出的 SwiftUI 无疑是最受关注的一项技术,它可以在所有 Apple 平台上构建出色的用户界面。而今年我们迎来了 SwiftUI 2.0,让我一起来看看 SwiftUI 有哪些新增功能。

今年有很多新 feature,且数量远超出我们一次演讲所能涵盖的范围,但是我们将尽力涵盖尽可能多的内容。同时我们也会介绍相关的 Session,大家可以查看这些 Session 以了解更多信息。

Apps and Widgets

让我们从 Apps 和 Widgets 开始。

这是苹果第一次仅使用 SwiftUI 来构建整个应用程序,而不是将 SwiftUI 代码嵌入 UIKitAppKitWatchKit 应用程序中。

我们来写一个简单的 Hello World! 应用。

你没有看错,这是100%正常运行的应用程序。

App & Scene

SwiftUI 将大量智能,自动以及可自定义的行为统统打包到了简单灵活的声明式 API 中。

我们先来展示一个用于跟踪正在阅读书籍进度的应用程序。

在底部,有一个自定义视图来表示应用程序的主用户界面,而顶部使用该视图作为应用程序主窗口的内容。

首先要注意的是这两个声明的相似之处。我们都定义了一个遵循 protocol 的结构。

我们设计了 SwiftUI 的新 App API,以遵循您在视图代码中已经习惯的声明式的 State-Driven 模式。 App protocl 使我们能够轻松地用一个结构来替换 AppDelegateSceneDelegate,该结构将管理我们的场景和应用生命周期。

另一个关键点在于 body 属性的返回类型。这里返回的 Scene 也是 SwiftUI 中的一个新概念,它表示界面可以由不同的平台独立显示。

WindowGroup

WindowGroupScence 的具体应用之一,它是 SwiftUI 中 Scene 如何提供现成的智能,多平台功能的有力示例。

在我们的 iOS 应用中,WindowGroup 正在为我们的应用创建和管理单个全屏窗口。相同的代码也可在 watchOS 上运行,还可以管理单个全屏窗口。

尽管这两应用看起来有所不同,但是两个平台上的核心应用结构相同,因此它们可以共享一个应用声明。

实际上,我的应用程序也可以在 tvOS 和 iPadOS 上运行。

由于 iPadOS 支持多窗口应用程序,因此我们免费获得了一些附加功能;就像能够创建可以并排显示的应用程序的多个实例。

这也扩展到了 macOS,它也支持多个窗口。我们可以使用标准的 Command-N 快捷方式创建新窗口,并将它们收集到单个选项卡式窗口中。SwiftUI 甚至会自动将 “新窗口” 菜单命令添加到我的主菜单中。

SwiftUI 还支持其他类型的场景,这些场景可以像视图一样组合在一起,构建出更复杂的应用程序。

就像 macOS 上新增的 “设置” 场景一样,“偏好设置” 窗口的添加也很简单。

设置场景将自动在应用程序菜单中设置标准的 “首选项” 命令,并为窗口提供正确的样式处理。

DocumentGroup

SwiftUI 的 Scene API 还支持基于文档的应用程序,例如我们为绘制矢量形状而构建的该应用程序。

新增的 DocumentGroup 场景,它可以自动管理 iOS,iPadOS 和 macOS 支持的,基于文档的场景的打开,编辑和保存。

在 iOS 和 iPadOS 上,如果未提供其他主界面,DocumentGroup 将自动显示文档浏览器。

在 Mac 上,DocumentGroup 将为每个新文档打开一个不同的窗口,并自动将命令添加到主菜单中以执行常见的文档操作。

说到菜单命令,SwiftUI 允许您使用新的 .commands 修饰符添加其他命令。

例如,在这里我们添加了一个自定义形状菜单,用于向画布添加新形状。macOS 将在主菜单的正确区域中自动添加自定义菜单,并显示其键盘快捷键。这些快捷键是我们使用新的 .keyboardShortcut 视图修饰符分配的。

.commands API 所提供的功能比我们在此处显示的功能要多得多,例如能够基于用户焦点来定位命令等等。

Xcode Support

为了帮助您构建这些新应用,我们还添加专门用于 SwiftUI 应用的新的 Multiplatform 模板,更新了 Xcode 中的新项目体验。

这些新模板针对多平台代码进行了优化,可自动为共享代码以及特定于平台的组件和资源。

另一部分是如何配置应用程序的 LaunchScreen

infp.plist 中新增的 “LaunchScreen” key,可以声明启动屏幕组件的各种组合。例如默认图像,背景色以及顶部和底部的空白栏(如上图配置的)。

如果在现有项目中已经使用了 Storyboard,仍然可以正常工作无需更换。不过对于新的 SwiftUI 项目,LaunchScreen 配置是一种简单的选择。

SwiftUI App Sessions

关于 SwiftUI Apps 我们已经准备了演讲,以更深入地介绍什么是场景以及它们与应用程序和视图的关系:

Widgets

现在让我们简单谈谈 Widgets ,这是 iOS,iPadOS 和 macOS 上的新作之一。 Widget 只能使用 SwiftUI 来构建的。它直接将 Swift 语言和 SwiftUI 的重要程度提升了一大截。

我们可以使用符合 Widget protocol 的自定义结构来构建 Apps 和 View 一样的 Widget。

我们可以制作许多种类型的 Widget,例如这种会定期向我推荐新专辑的 Widget。

Widget 也可以配置其他类型的数据,例如 Siri Intents。

关于构建 Widget,有很多要讨论的内容,并且我们有一些讲座可以帮助你入门:

建议先观看 Build SwiftUI views for widgets 以了解更多信息。

Widgets in watchOS

我们现在可以使用 SwiftUI 为 Apple Watch 构建复杂的自定义功能。可以像我制作的本周咖啡图表那样构建全彩色功能。

关于构建复杂的 watch 应用可参考如下 Sessions:

Lists and Collections

接下来,让我们讨论对 Lists 和 Collections 的改进。列表是许多应用程序的重要组成部分,通常代表用户与之交互的主要界面。在此版本中,Lists 获得了一些很棒的新功能。

Outlines

使用 outlines,我们只要简单声明具有动态性、数据驱动的内容就可完成常规列表的展示。

还可以在初始化时提供 .chldren 的 KeyPath,来实现列表的嵌套显示。

默认情况下,Lists 使用 macOS,iOS 和 iPadOS 上的系统标准样式来显示。

我们希望通过易用性的 outlines 可以帮助和减少偏内容的应用程序对于 push-pop 导航栏模式的滥用。

Grids

去年 SwiftUI 被人诟病最多的恐怕是对列表和网格的支持不友好。当我们有数百或上千个 Views 存在于 GridsStacks 中时,SwiftUI 可是会直接初始化并创建它们,这可是非常 😱 的事情。

好在今年,SwiftUI 增加了对网格布局的 Lazy-Loading,可以将其与 ScrollViews 组合来实现网格内容的平滑滚动。

网格具有强大布局功能,支持多种配置,例如调整列数以适应可用空间,就像我们在此处看到的横向和纵向一样。

或强制固定数量的列,每个列都可以具有自己的大小调整参数,例如下面示例,每列固定为 4 栏。

当然,SwiftUI 也支持水平滚动的网格。除此之外,我们还提供了 VStackHStackLazy-Loading 版本,这些版本非常适合构建自定义的可滚动布局。

例如,这个图像瀑布流的展示:

我们使用 LazyVStack 作为容器,同时利用 ViewBuilder 新增的 switch 控制功能,轻松支持图片的各种布局。例如顶部的单个大图,中间的多个小尺寸图片的不对称组合等。这些组件一起组合成一个无缝的图片流。

在本文中,我们只是简单介绍了部分功能。还有更多新增的 View,比如 ScrollViewReaderTextEditor 等等。想要了解更多 SwfitUI 的 Lists 和 Grids 的强大功能,请查看 Stacks, Grids, and Outlines in SwiftUI.

Toolbars and Controls

现在,让我们跳到 SwiftUI 对 toolbar 的支持以及自定义控件的新方法。

Toolbar

从 macOS Big Sur 中漂亮的外观到更新的 iPad 系统体验,再到 watchOS 上的 primary actions。今年,我们可使用 SwiftUI 新增的 .toolbar 修饰符构造所有这些API:

```swift
// Toolbar
BookList()
.toolbar { // #1
Button(action: .recordProgress) {
Label("Record Progress", systemImage: "book.circle")
}
}

BookList()
.toolbar { // #2 和 #1 是同等效果
ToolbarItem(placement: .primaryAction) {
Button(action: recordProgress) {
Label("Record Progress", systemImage: "book.circle")
}
}
}

BookList()
.toolbar { // #3
ToolbarItem(placement: .confirmationAction) {
Button("Save", action: saveProgress)
}
ToolbarItem(placement: .cancellationAction) {}
Button("Cacnel", action: dismissSheet)
}
}
BooDetail()
.toolbar { // #4

top Created with Sketch.