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,我们只要简单声明具有动态性、数据驱动的内容就可完成常规列表的展示。11-outlines-recursive

还可以在初始化时提供 .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:

 1// Toolbar
 2BookList()
 3	.toolbar { // #1
 4		Button(action: .recordProgress) {
 5			Label("Record Progress", systemImage: "book.circle")
 6		}
 7	}
 8      
 9BookList()
10	.toolbar { // #2 和 #1 是同等效果
11		ToolbarItem(placement: .primaryAction) {
12         Button(action: recordProgress) {
13				Label("Record Progress", systemImage: "book.circle")
14      	}
15		}
16	}
17      
18BookList()
19	.toolbar { // #3
20		ToolbarItem(placement: .confirmationAction) { 
21			Button("Save", action: saveProgress)
22		}
23      ToolbarItem(placement: .cancellationAction) {}
24			Button("Cacnel", action: dismissSheet)
25		}
26	}
27BooDetail()
28	.toolbar { // #4
29      ToolbarItem {
30         Button(action: recordProgress) {
31            Label("Progress", systemImage: "book.circle")
32         }         
33      }
34      ToolbarItem(placement: .bottomBar)
35      	Button(action: .shareBook) {
36				Label("Share", systemImage: "square.and.arrow.up")
37      	}
38      }
39	}

上述代码的效果如下:

上面代码列出了构造一个 toolbar 的多种方式。toolbar items 与 SwiftUI 中的其他视图组成基本相同,在上面的几个例子中,均由 button 实现的。

另一个示例是 principal 语义的使用,它将在应用程序的突出位置中显示,就像我们下面看到的:

toolbar 默认会放置在我们熟悉的位置,也可以通过使用 toolbar 进行显式自定义。

我们只需在 SwiftUI 描述这些 toolbar item 的状态,然后 SwiftUI 会根据语义的定义自动找出正确的位置。例如,wathcOS 中的确认和取消操作就会置顶显示。

我们也可以指定放置的位置来进行额外的控制,尤其是对于相对较小的屏幕。例如,在 iOS 中通过指定分享 item 的 .bottomBar 位置等。

Label

在上面的示例代码中,你应该发现了 SwiftUI 中 Label 视图的新用法:

1Label {
2	Text("Progress")
3} icon: {
4	Image(systemName: "book.circle")
5}

Label 现在可以是 TextImage 的组合,我们不仅可以使用国际化的 Key 来配置不同语言的标题,还能利用 system imageSF Symbol 来设置图标。想了解更多 SF Symbol 2.0 请访问 Session - 10207

对于前面的 toolbar 的示例,Label 默认仅会在按钮上显示出图标,而文本则用于辅助的目的。这一行为同样作用于 menu list

Label List

我们的 Label 支持以列表展示啦。无论图标大小如何,本文都完美对齐,并且对于不同大小的的动态类型字体,Label 也能发挥作用。 来看一段代码:

1List {
2	Label("Intruduction to SwiftUI", 
3         systemImage: "hand.wawve")
4   Label("SwiftUI Essentials", 
5         systemImage: "handstudentdesk")
6   Label("Data Essentials to SwiftUI", 
7         systemImage: "flowchart")
8}

Label 默认以 large 尺寸的布局展示。当更改为 extraLarge 时,图标和标题都会自动更新,而且能很好地重排文本和增加列表行。当然,还支持可以更大尺寸的 accessibilityLarge

对于更大的字体,Label 会更新为文字环绕,以最大程度地增加可见文字的数量。

对比效果如下:

Help

现在,随着 toolbar 之类具有干净的仅图标样式的标签元素,为这些元素提供额外的帮助比以往任何时候都更加重要。

使用新的 .help 修饰符,可以附加控件的作用说明,并在 macOS 上显示为工具提示。

同样,此修饰符可在所有平台上使用,因为它还提供了可访问性提示,可为您的应用程序在任何地方提供更好的 VoiceOver 体验。

New Views

ProgressView

SwiftUI 现在支持 ProgressView 了,它不仅支持线性样式,还带来了环形样式。

1ProgressView("Downloading Photo", value: percentComplet)
2	.progressViewStyle(CircularProgressViewStyle(tint: .blue))

Gauges

对于 watchOS 7+ 现在可以在 SwiftUI 获取仪表盘视图了。

对于 Guage 视图,我们可以对 minimum 和 maximum 值进行可选设置来控制是否显示。

New Effetct and Styling

接下来,让我们看一下使用 SwiftUI 来制作一些炫酷的效果。

HoverEffect

macOS Big Sur 对 Notification Center 和菜单栏中的新 Control Center 均使用 SwiftUI 重写,同时 Control Center 正是使用了 SwiftUI 中的一项动效,实现了其在不同模块间平滑切换。

接下来,我们构建了一个 UI 原型来展示该效果的使用。

这里以收集喜欢专辑的功能来展示,它由相册的 LazyGridselectdAlbumRow 组成。只需为每个按钮添加 matchedGeometryEffect 并绑定上每个专辑的 ID 和相对的 NameSpace。这样当我们选择喜欢的专辑后,它会从网格中流畅地过渡到底部我们喜欢的专辑那一行。

除了这个配预设效果之外,我们也可以通过提供的 API 配置自定义动画:

Styling

SwiftUI 也新增了很多方便的样式修改功能。

ContainerRelativeShape

我们从专辑的 widget 中可以看到,新增的 ContainerRelativeShape 将采用最接近所包含内容的形状来生成相似的路径。

它有效的将该外部容器的形状进行偏移,并根据其偏移量自动保持与内容形状的同心度。

还有一些其他改善和增强文本元素体验的效果等。

Dynamic Type Scaling

现在在 SwiftUI 中,图像可以作为文本的一部分嵌入其中,并且作为统一的整理来响应动态的变化。

这里其实是通过一个新的属性包装器 ScaledMetric,来描述自定义的非文本度量标准 (例如布局) ScaledMetric 会根据当前的基准值来自动缩放尺寸。

 1// Dynamic Type Scaling
 2@ScaledMetric private var padding: CGFloat = 10.0
 3VStack {
 4	Text(albmu.nmae)
 5		.font(.custom("AvenirNext-Regular", size: 30))
 6		
 7	Text("\(Image(systemName: "music.mic")) \(album.artist)")
 8		.font(.custom("AvenirNext-Regular", size: 17))
 9}
10.padding(padding)
11.background(...)

缩放效果如下:

总之,通过这些使得基于响应式的自定义布局变得很容易,同时又具有良好的表现。当然,我们能做的不止于此,即使是系统控件,也能随心所欲的自定义它们的样式。

哦,对了。颜色也是同样可以自定义的。

List Item Tinting

最后再介绍一个列表元素的 tineColor,现在我们可以针对不同的 item 轻松配置不同的 tintingColor。

 1List {
 2	Section {
 3		Label("Menu", systemImage: "list.bullet")
 4		Label("Menu", systemImage: "heart")
 5			.listItemTine(.red)				
 6		Label("Menu", systemImage: "seal")
 7			.listItemTine(.purple)		
 8	}
 9	Section(header: Text("Recipes")) {... }
10		.listItemTine(.monochrome)
11}

对比效果:

System Integration

最后一节,来简单聊一聊,SwiftUI 与系统的深度集成。

链接跳转

通过新提供的 Link View,URLs 跳转现在也是 SwiftUI 中的一等公民了。它默认会使用浏览器打开链接。

同样,应用间的 Universal Link 跳转也支持啦,可以通过 openURL 来实现不同应用程序的访问。

除此之外,还有包括 Drag and DropUniform Type Identifiers frameworksSign In With Apple 等各种功能等,就不一一介绍了。

总结

很高兴看到 SwiftUI 的下一个迭代是如此强大和丰富的功能。

  • 通过提供更加简洁易用的 protocol,如 AppScene 使得基于 SwiftUI 来构建应用程序变为了可能。
  • 通过全新的 Widget API 的支持,使得 SwfitUI 从一个简单玩具模型提升到可以成为实用工具的一部分。
  • 随着各种新的视图的出现、功能的完善和性能的提升,SwiftUI 将具有无限想象力,或许不久的将来会直接替代 storyboard 并且成为可视化编程的中流砥柱 😂。

知识点问题梳理

这里罗列了四个问题用来考察你是否已经掌握了这篇文章,如果没有建议你加入收藏 再次阅读:

  1. 都有哪些协议遵循 Scene,它们都有哪些功能和使用场景 ?
  2. 说说 toolbar 有哪些构造方法 ?
  3. 本文提到了哪些新增加的视图 ?
  4. 本文提到了新增加了哪些与系统交互的功能 ?

相关阅读