大多数浏览器和
Developer App 均支持流媒体播放。
-
SwiftUI 基础知识
初次深入了解如何利用 SwiftUI 构建 app。了解视图和它们的工作方式。通过 SwiftUI,您可以更加快速、轻松地创建出色的用户界面,包括基本控件到列表和导航堆栈等复杂容器。了解按钮等基本控件如何做到简单与多用途兼具。探索如何利用 SwiftUI 将这些组件组合成为更大的、功能全面的用户界面,以构建出色的 app。学习 Apple 全新声明式框架的基础知识,培养您的 SwiftUI 技能。
资源
相关视频
WWDC21
WWDC20
WWDC19
-
下载
早上好 早上好 欢迎来到 SwiftUI Essentials 项目 我是 Matt Ricketson 我目前在做 SwiftUI 项目 稍后 我的同事 Taylor 也会为大家讲解 那么目前大家觉得 SwiftUI 怎么样呢 我也是 今天我非常激动 能和各位聊一聊 SwiftUI 我们要讲很多内容 所以让我们开始吧
SwiftUI 是一个新框架 旨在为大家构建优秀 App 提供最优捷径 这意味着为你提供构建 优秀用户界面的最短路径 虽然 SwiftUI 是一个 新框架 但其中的很多内容 对你来说已经很熟悉了 这是因为它包含了 UI 框架的 所有基本组件 它有按钮和文本字段等控件 它有像栈和列表 这样的布局容器 它有绘图 动画和手势 SwiftUI 甚至支持 特定于平台的概念 比如 Mac 上的菜单 Apple Watch 上的数字表冠 和 Apple TV 上的 Siri Remote 遥控器 因此 我们要记住的是 我们并非试图利用 SwiftUI 重新发明轮子
但我们都知道 事实是 仅仅知道如何使用 这些组件并不足以构建 一个出色的 App 因为一个出色的 App
它必须要易于使用 并且支持动态类型
它需要适用于不同设备 屏幕大小和输入类型 它还需要像交互式动画 这样的东西和对深色模式和拖放等 系统功能的支持
这些可以让 你的 App 接触到 尽可能多的用户 并让它紧跟潮流 我们都知道这 不是全部的内容
因为你当然也添加了 你自己独特的功能 让你的 App 脱颖而出
所以我想花点 时间重申一下 我们还有很多东西需要学习
需要编写的代码和维护的 东西很多 那么 SwiftUI 是如何帮你完成这些工作呢
请各位先想一下自己的 App
首先 你有大家希望 从你的 App 中获得的 基本功能 比如控制和导航 易于使用并根据不同 的设备调整布局
我们需要做这些事情 我们需要它们来构建 一个真正出色的 App
但还有一些对于你的 App 来说令人激动的 独一无二的自定义功能 这些也是有趣的功能 我们为这些有趣的 功能倾注了热情 我们也为能构建出它们而感到骄傲
所以 SwiftUI 的目标 非常简单 我们希望 你把尽可能多的时间 花在有趣的功能上 而不是花在基本功能上 在不用牺牲质量前提下
这就是我们所说的 构建优秀 App 的最短路径 因为各位都已经在 构建优秀的 App 了 我们只是想让大家更快地达成目标
本次演讲是为了让大家 更好地了解 SwiftUI 我们会看一些代码 但我们也会讨论 SwiftUI 的设计以及 它如何能帮你构建更好的 App
本次演讲过后 你就能够使用 SwiftUI 构建一个完整的用户界面 我们首先从视图和 修饰符的基本内容讲起 那么我们将需要一个例子 我总是试着选择 一个我关心的例子来激励我
如果大家最近上网 可能会看到千禧一代 比如像我自己 把这个东西当成我们 生活中最重要的部分
没错 牛油果吐司 看来我们的观众里有千禧一代 所以今天我们要做一个 App 来点牛油果吐司 我已经做了一些内容 所以它现在看起来 是这样的
这是一个简单的形式 让我能立刻从手机上 点我想要的食物
显然 它目前为止还没有 很多内容 但我们会在 整个演讲中继续构建
但在深入研究代码之前 我想先 谈一下视图 这是因为视图是用户 界面的基本构建块 它对我们构建 SwiftUI 每一环节都非常重要
如果你之前使用过其他 UI 框架 比如 UIKit 或 AppKit 你可能已经 听说过视图这一术语
SwiftUI 也有视图 它们的主要作用与它们在 这些框架中的作用相同
从高层级来讲 视图只是定义 UI 的一部分 当你看一个 App 时 你看到的一切都是 由视图定义的
单独的控件是视图
容纳它们的容器也是视图 事实上 你在屏幕上 看到的每一个像素都可以 以某种方式追溯为视图 我们通过将这些 视图组合成控制架构 来构建用户界面 从底部的容器 到底部的文本 图像和形状
如果你以前用过 UIKit 或 AppKit 应该很熟悉这幅图 重要的是要理解 SwiftUI 的视图也是如此 SwiftUI 可能与你 习惯的不同之处在于 视图在代码中的表达方式 我们来看一些代码 在我们的示例 App 中 我们只有一个垂直栈包括控件和文本 通过代码很容易看出这一点
但实际上 你会注意到 左边的代码与右边的 等价视图层次关系 视图非常匹配
在栈的根目录中 栈包含了文本和控件 指向每个控件中 包含的单个文本标签
现在你看不到的是对函数的调用 比如添加子视图
因为我们不是 一点一点地构建视图层次 而是将它初始化为 一个完整的组合结构
这是因为 SwiftUI 以声明的方式定义 其视图 而非强制 我想不出比 牛油果吐司更好的 类比来解释这些概念了 所以让我们试着用命令式代码做牛油果吐司
命令式代码包括 通过发送显式命令来构建结果
这有点像在电话里 教朋友做牛油果吐司
你开始告诉他们 需要什么原料 什么设备 然后开始 指导他们做吐司和切牛油果 所有这些指导 开始变得有啰嗦 如果你的朋友 搞砸了任何一个小步骤 比如忘记烤面包 那么 最后的结果就毁了
现在让我们把它和 牛油果吐司做个比较
声明代码包括通过 描述你想要的内容 然后让其他人来想办法如何为你构建结果 这有点像从牛油果师傅 那里点牛油果吐司
幸运的是 加州有很多这样的面包店
现在你要做的就是 说出你想要的 你甚至可以插入自定义指令 就是这样 因为有专家为我们制作 所以我们能保证 得到高质量的结果 现在让我们继续看一下代码 SwiftUI 将充当专家的角色 随时为您提供帮助 在代码中 我们通过 初始化编码这些关系的结构 来声明视图之间的架构关系
SwiftUI 会将你的 视图转换为屏幕上 呈现的结果
关于这个还有很多要讲 但是现在让我们先 习惯一下代码中的语法 我们将从容器视图开始
容器视图声明为 作为其内容的 其他视图的组合
这些内容视图是在一种 称为视图构建器的特殊 类型的闭包中声明
比如 我们已经看到了 VStack 或垂直栈 这是其中一个容器的例子 视图构建器允许我们 在闭包中编写声明代码 我们可以在闭包中 列出我们的内容 不是调用 像 AddSubViews 这样的函数 为了进一步了解 它的工作原理 让我们 看一下 VStack 的实际 API 你可以看到内容参数 被定义为一个闭包 但使用这个 ViewBuilder 属性进行了标记
Swift Compiler 知道如何 将此属性标记的闭包 转换为一个新的闭包 该闭包返回一个 表示栈中所有内容的视图
这是 SwiftUI 使用 Swift 功能帮你编写 更少代码的一个例子
像 VStack 这样的视图 还可以接受除内容之外的其他参数 例如 我们可以配置 我们的 VStack 使其内容沿着前缘对齐 而不是使用默认的中心对齐
总之 这是一种 非常好的自然语法 允许我们使用大括号 和缩进来区分 容器视图及其 配置和其中的内容
我们也遵循许多控件的语法 因为 SwiftUI 里的大多数控件也是容器
这在我们的实例 App 中可以看到 在每种情况下 我们的控件 都定义了一段文本作为 它们的标签 用于描述它们的用途
现在我们在这不仅可以放文本 我们可以放任何一种视图 我们将在稍后对此 进行更深入的讨论
这里看到的另一种 语法是 Toggles 和 Stepper 命令中的美元符号
前面的美元符号 表示我们正在向 控件传递一个绑定 而非一个普通的值
那么什么是绑定呢 在我们的示例 App 中 Stepper 包含在一个视图中 该视图依赖于持续 追踪当前订单的状态 它使用 @State 属性 来定义订单的特性
当 SwiftUI 看到用 该属性标记的特性时 它会自动在后台创建 并持续管理状态 然后通过该属性 公开该状态的值
在本例中 它包含 一个我自己定义的 struct
它表示我们所有的订单信息
如果我们只想读写 @State 的数据是很简单的 我们可以直接读写属性
当我们为 Stepper 做 标签时 我们就这样做了
然而 Stepper 还需要 在按钮被轻点时的状态 能够编辑 State
我们使用这个美元符号前缀来表示 我们应该传递一个绑定到 Quantity 属性中 而非只传递一个只读值
绑定是一种托管参考 允许一个视图编辑 另一个视图的状态
现在 要了解更多关于状态 和绑定以及如何管理 App 中使用的所有 其他类型的数据依赖 我强烈建议 大家看一下视频 《Data Flow Through SwiftUI》 但是现在 重点是 要记住如果你曾经 看到过一个属性 比如 @State 它通常表示 SwiftUI 在幕后代你管理的某种 数据依赖关系
如果你看到一个美元符号前缀 那通常表示我们在 传递一个被绑定的参数 到另一个视图
回到我们的实例 App 还有一个更重要的 语法我们还没有讨论 你可以看到我们在上面 为标题设置字体
让我们把它放大 首先初始化文本
这也是 SwiftUI 另一种视图
然后 我们在 Text 文本上调用一个方法 并将 系统定义的文本传递给它 这种方法在 SwiftUI 中称为修饰语句 修饰语句只是一个 从现有视图创建 新视图的方法
我的意思就是
这是没有 font 修饰语句时 UI 的样子 在这种情况下 只呈现默认的主体字体
这就是视图架构 关系图的样子 我们看到 VStack 中包含了我们的文本
当文本被修改时 将插入一个新视图 来包装现有的文本 新视图指示 SwiftUI 用新的 front 来呈现文本
这些修饰符甚至 可以链接在一起
例如 我们可以通过 添加前景颜色修饰语句 来更改标题的文本颜色
这将为视图架构中添加 另一个视图 该视图将包装 修饰语句视图
显然 我们的视图架构 很快就会变大 对于经验丰富的 UI 程序员来说 这可能 会为他们做内部 控件时提个醒 因为多年来 我们 训练自己通过让 视图层级架构尽可能 保持小和轻从而来 优化 App 的性能 但请记住 我们是在写 定义代码
SwiftUI 是我们的专业指导 它会根据我们的意见 根据我们点的菜 熟练地编写出代码 因此 即使我们必须将 文本包装在多个封装器 视图中 SwiftUI 也会 在后台将其重叠成 一个高效的数据结构 然后供渲染系统使用
你无需担心会 影响性能 你会 发现这种链接 修饰语句实际上 提供了很多好处
比如 修饰符链能够 强制执行视觉效果的确定顺序
现在我们看到的是一段 以绿色为背景的文字 但是文本看起来有些过多 所以让我们试着 通过在文本周围添加一些 间隔来扩展背景
我们添加了间隔修饰符 你可以看到它在视图层级中 添加了一个新视图
但屏幕上没有任何变化
实际上间隔就在这里 只是我们看不见而已
我们来看一下代码 我们的背景修饰符只是把 文本包了起来 而没有改变间隔 这表示间隔被 应用到了背景之外
幸运的是 通过移动 背景修饰符来 包装文本和添加间隔 能轻易解决这个问题
现在让我们再来看一下 我们刚才做的事情
想象一下 如果间隔 和背景是文本上的属性 而不是单独的修饰符
这种情况下 如果没有 反复试验或阅读过文档 我们将无法知道 他们的申请顺序
相反 通过像这样将修饰符 链接在一起 我们可以 直接显示顺序
我们也把它设计的很易于 自定义 就像刚才那样
现在 这些修饰符的 另一个好处是它们可以 在视图之间共享 例如 这里我们对多种 不同类型的控件 应用了不透明效果
我们甚至可以将 不透明度应用于整个栈 而不是每个单独的控件
这些视图都不需要定义 自己的不透明度属性 这意味着它们 可以拥有更简单 更集中的接口
这些就构成了 SwiftUI 的整体原理
即倾向使用较小的 单一用途的视图
这些更简单的视图 更容易理解 也更容易在更长的时间内维护
一旦你得到了所有 这些小视图 你就可以把它们 组合在一起来创建更大 更复杂的视图
SwiftUI 的整个框架 都是围绕着小的代码段的 的组合而设计 你应该用同样 的方式来组织你的代码
所以你可以从 简单的文本开始
你可以把它修改的更好 你可以把它们组合在一起 来创建一些优秀的程序
比如像这个 牛油果吐司 App 就我个人而言 我迫不及待地想看到大家 都来使用 SwiftUI 构建用户界面
但在此之前 我们首先需要知道如何 构建我们的自定义视图 现在让我们来创建一些新内容
来看一下我们的 App 我很想看一下我以前的订单历史
我已经草拟了一个设计方案 它只是一个简单的列表 显示了每个订单的摘要 以及我选择的配料的一些图标
我已经写了一些代码 让我们一步一步 快速地来看一下
首先 我定义了一个 名为 OrderHistory 的 新视图为一个符合视图协议的结构
我们接下来还会讲到
我的视图只有一个 输入属性 即 previousOrders 它只是我所有订单信息的集合
我的视图有一个计算属性 名为 body 它能够传回视图的内容
这里我们使用的 sum 关键词是 Swift 功能 它允许 Swift 自动推断传回类型
body 属性会传回一个列表 该列表通过将之前的 每个订单标记到一个 新视图集合来生成 其内容 每个订单使用 另一个进行追踪的视图构建器
现在我们已经理解了 这段代码 让我们回过头来 深入了解一下 SwiftUI 为什么以这种方式定义自定义视图
让我们从视图是如何能 遵循视图协议的结构开始讲 如果你用过 UIKit 或者 AppKit 你可能已经 习惯于将视图定义为 用于将视图定义为继承 公共视图超类的类 而不是符合协议的结构 例如 UIKit 中的自定义 视图继承了 UIView 超类
UIView 为 alpha 和 backgroundColor 等常见 视图属性定义了存储位置 假设我们不用 SwiftUI 而用 UIKit 来构建 OrderHistory 我们的自定义视图将 继承 UIView 的存储属性 并为它自己的自定义 行为添加更多的属性
那么 SwiftUI 和这个有什么不同呢 请记住 在 SwiftUI 中 我们将这些相同类型的 公共视图属性表示为单独的修饰符 就像我们对不透明度 和背景所做的那样 每个修饰符都创建了自己的视图
这意味着这些属性的 存储位置 分布在我们的 视图层级架构中 的每个修饰符视图中 而不是被每个 单独的视图继承
现在 这能让我们的视图 变得更小 优化其存储空间 以满足其各自的用途
在这个世界上 让视图成为协议是非常 有意义的 因为它不再 需要为所有视图 提供公共存储模板 但这个视图协议实际的用途是什么呢 让我们记住 视图的概念定义
视图定义了 UI 的一部分 我们通过组合小视图来 构建更大的视图 这就是视图协议的作用 它定义了一个视图架构 给了它一个名称 这样就可以组合 并重用你的整个 App 每个具体类型的视图 只是一个封装的其他视图 代表其内容在其 主体属性和所有
必需的输入来创建 这一由其属性呈现的视图
现在实际协议 只定义了传回另一种 视图的主体属性
但是仔细看一下 这个定义 有些人 可能会问这不是递归的吗
如果我有一个视图 它将 body 定义为 另一种视图 那么这个视图 将把它的 body 定义为另一种视图 它必须要结束 对吧 它不能永远进行下去
这样做的原因是 SwiftUI 提供了多种原始视图 即没有任何 内容的视图 这些视图表示所有其他 视图都构建在这些 原子构建块之上
我们已经看到了文本 图像是原始视图的 另一个例子
SwiftUI 还提供了 用于绘制颜色和 形状等基本元素和像 Spacer 一样的基元 实际上 你可以用 SwiftUI 中的原始视图 来绘制一些非常复杂的图形
要了解更多这方面的内容 请大家看一下演讲 《Building Custom Views in SwiftUI》
我们的示例使用了文本
但是我们的列表实际上 添加了它自己的原始视图 可以把它视为我们 每一行之间的分隔符
现在我们还可以看到 自定义视图被定义为 struct 而不是类 这又回到了 SwiftUI 中定义视图的方式
在本例中 这表示 视图不是我们使用 基于命令事件的代码 随时间更新的永久目的
相反 我们的视图被 定义为其输入的函数
因此 无论何时我们的输入 发生了变化 SwiftUI 都会再次调用主体属性 来获取视图的更新版本
我们这里用的 List 是定义代码强大功能 的一个很好的例子 如果我们的 previousOrders 集合 改变了 SwiftUI 将 比较列表的新旧版本 并根据更改内容 高效地更新屏幕上 呈现的结果
比如 我一直在为 我的 App 开发云同步 对我来说 能够在我所有的 设备上都能使用牛油果吐司 的相关数据至关重要
让我们看看如果另一个 设备从我们的历史记录中 添加和删除订单会发生什么
大家看到在右边的是 SwiftUI 自动区分 集合中的更改 并合成插入和删除操作 然后把它们用默认 动画呈现出来 这些都是无需编写 任何额外代码就可以 免费获得的功能 这个功能真的很赞 这个功能很实用的原因在于 你不需要自己一直 亲自管理呈现状态
现在你可以根据主体属性 中的当前数据为 视图生成新值
你可以让 SwiftUI 为你在这两个版本之间 生成必要的更改
这就是定义代码的强大之处
那么让我们继续来构建 orderHistory 视图的剩余部分 如果大家回想一下 我们最初 的设计就包含了这些图标 用来在订单中 添加额外的配料 比如盐和红辣椒片 让我们从盐的图标开始 首先 我们将在文本后面添加 一个带有 Spacer 的水平栈
然后我会在订单中 需要盐时给大家展示我的 SaltIcon 视图
正如你在这段代码中所看到的 我们前面讨论过的 ViewBuilder 语法允许我们 使用自然控制流 比如 if 语句在视图 应该包含在栈中时 以定义的方式来定义视图
在定义性代码中 使用这样的 if 语法 非常自然 但是也还有其他方法 可以在视图中编写条件代码 重点是要选择正确的工具才能 在屏幕上得到正确的结果 让我们看一个简单的例子 来理解一下我的意思 我为我们的 App 新建了 另一个屏幕 你可以选择 正常和倒装的 AppIcon
我的第一步是编写 一个自定义视图 它接受倒装状态作为输入 并根据我的状态应用 一个旋转修饰符
然而 当我们试图翻转这个 图标时 会看到一个不太 美观的交叉淡入动画 这是因为我们的代码 让 SwiftUI 在两种 不同的视图之间切换
包含在旋转修饰符中的视图 相对于 AppIcon 其本身 默认情况下 SwiftUI 会在 添加和删除视图时淡入和淡出视图 这就是为什么我们会得到 这种交叉淡入效果
现在我想让图标 在翻转时旋转 为了达到这一效果 我要用一个 rotationEffect 修饰语句来定义一个视图 并根据自身状态来对它的输入进行配置 通过在修饰语句中 定义我们的条件 SwiftUI 可以提供 更好的默认动画 将图标旋转到新的方向
这里需要学习的是 你应该尽可能地 把你的条件添加到 修改语句中 因为这可以帮助 SwiftUI 检测这些变化并为你提供 更好的动画效果
如果你的目的是在层级 架构中添加或删除视图 那我们之前看到的 if 语法就十分有用了
回到我们的示例 App 我们的 OrderHistory 视图开始变大了 所以最好能把它 分解成更小的部分 那么让我们试着将每个 列表行的代码分解到它 自己的自定义视图中
首先 我将创建一个名为 OrderCell 的新自定义视图 现在这个视图需要一个 body 部分 幸运的是 我们已经在 OrderHistory 视图的列表中构建了它 下面我们把这段代码移过来
为了生成 OrderCell 的主体 我们需要为其输入数据 我们还需要添加 一个属性来表示它
最后 我们将为 列表中的每一行创建 一个新视图实例
这里的重点是我们能 很容易地将 UI 分解成 更小的部分并将代码 分解成新的视图 请记住 使用定义性代码 来添加新的封装器视图 实际上是可自由操作的 因为 SwiftUI 会在后台 对它进行优化 所以重要的是 你不需要再为 得到更多成果并 让 App 表现得更好 和组织你的视图 代码之间妥协 所以 让我们以添加 红辣椒粉图标来作为结束 只要像之前那样加上 另一个条件就可以了
这是可行的 但它的 可扩展性不是很好 如果我们以后添加新的佐料 我们将不得不在代码中添加 带有新条件的佐料 最棒的是可以 有条件地从订单数据来 生成一组图标
要生成视图集合 可以 使用 ForEach 视图
就像我们的列表一样 ForEach 会接受一组数据和一个 ViewBuilder 后者将每个数据项显示到中 它自己的视图
但与列表不同的是 ForEach 不添加任何自己的视觉效果
而是将自己的 内容添加到容器中
所以这段代码要好得多 因为现在我们订单的 历史记录将在未来自动 支持新的佐料 而无需向视图添加任何代码
比如 我们可以把鸡蛋 添加为第三个图标
退一步讲 我们 只用十几行代码 就能构建出 这么多的功能 是非常惊人的
更了不起的是 还有很多代码我们 都不需要写了
我们已经看到了 SwiftUI 是如何自动处理数据 的更改 它甚至能在 添加和删除数据时 插入默认动画
但我还没有提到我们的 App 也能适应动态类型
它甚至还支持深色模式 这些技术支持都是免费的 而且我们不需要编写任何额外的代码
这非常棒 这就是我们所说的 SwiftUI 能为你提供一条 创建 App 的捷径的意思
以上就是用 SwiftUI 创建自定义视图的方法
现在我想邀请 我的同事 Taylor 来和各位谈谈如何 充分利用 SwiftUI 为大家提供更有用的功能 感谢 Matt 大家好 现在 我们对自己的 App 都有了一个良好开端 Matt 构建了初始订单 表单和历史屏的显示页面 但有一个明显的问题就是 它看起来不太像我们 习惯的 iOS App 它们一般不是这些简单的 垂直控件栈 通常 这种类型的 UI 会更像大家在右边看到的那样 最大的区别之一是 控件周围的容器 本身具有这种标准化
的组列表样式 在 SwiftUI 里我们称它为 Form Form 是一个容器 就像 VStack 一样 但是它是专门为构建 异构控件的这些部分 而构建的 无论在什么 平台上它都提供 标准的外观和体验
现在我们已经定义了 我们想要在自己的 App 中的功能集 标题 Toggle Stepper 还有 Button
我们所做的就是将 容器本身从现有的 VStack 更改为一个 Form 然后我们就可以很容易地 添加一些部分来划分内容
正如 Matt 前面所讨论的 我们的代码会继续 显示反映生成的 UI
由于我们控件的核心定义 没有改变 我们的代码 实际上也不需要改变
只要将容器从 VStack 更改为 Form 控件就会 自动适应该上下文 从整体背景和 可滚动性到分隔每 个控件的行 甚至是 按钮之类的样式
这是 SwiftUI 再次关注渲染 这些元素的细节 并能让我们专注 于 App 的功能
还有一个从屏幕快照 上看不见的细微的变化
让我们仔细看一下按钮 你可以看到对齐 填充 和按钮周围的装饰都改变了 点按状态也展示出了 你从这样的 UI 中 所期待高亮效果 同时显示了相同的按钮定义
正如你可能期望的那样 这个相同的定义在其他 上下文中或其他平台上 也适用而且具有多种 可能的外观和体验 按钮还具有我们 在其他视图中看到的相同的 可组合性的固有能力 标签当然不限于 文本 它也可以是图像形式 它可以是我们定义的 任何类型的视图 甚至可以是图像和文本 的显式垂直栈 这种继承的可组合性 带来了各种可能性 并同时使按钮能够被简化为 两个基本属性
即它在激活时 执行的操作和 描述该操作的标签
这就是按钮的 整个 API 表面
当然 这并不是说 只有这两种方法 可以自定义按钮 就像我们之前看到的 并且将继续看到的 上下文和修饰符都支持 在 macOS 上添加更多 丰富的行为 从禁用状态 到按钮 样式甚至 控制的大小 但是这个核心定义加上 适应行为可以支持任何类型的按钮 随着时间的推移 在不同的平台上 我们看到了 很多不同的按钮 它们的变化不仅 取决于外观 还取决于 我们与它们的互动方式 从点按到轻点 到通过 开关控制或 Siri Remote 遥控器 来进行选择 但它们 都可以归结为一个 操作和一个标签
就像按钮一样 其中的 每个控件都具有相同的 适应行为能力
控件描述的是它们 所服务的目的或角色 而不是它们的外观 这使得它们可以 在不同的环境 和平台上重用并适应 这些情况 这也使得他们有 更小的 API 表面来满足 这个确切的角色 不过我们同时还需要 减少控件 而不是为可能 需要在其中使用的每个 上下文都配置一个控件
同时仍然支持非常 强大的自定义功能 比如完全重新定义 App 中按钮的外观 现在我们看到了这种 适应功能如何让我们 快速地从一堆简单的控件 转换为系统表单的标准外观和体验 但同样的适应性也使我们能够将 这些控制带到其他平台上 比如 watchOS 这样我们就可以 在忙碌中快速点到吐司
现在我们已经在使用的 另一个控件是 Toggle 各位已经看到了 SwiftUI 中的 Toggle 不仅仅 只是一个字面上的转换 不管它在什么 平台上都是这样的 就像按钮一样 Toggle 有两个基本属性 即开或关 以及描述 Toggle 整体目的的标签
同样 这也在结构 本身有所呈现与按钮的一个明显 区别是 它不执行操作 而是执行与 Boolean 值的绑定 这个绑定是到 App 中某个状态 或模型的直接 读写连接 允许 显示 Toggle 和 更新该状态或模型 无需手动响应某个操作 去提取值并在模型中设置它 它会为你把一切搞定
现在 Toggle 和其他的 控件在另一个非常重要的 方面也有一定适应性 对于一些人来说 UI 是一种视觉体验 而其他人可能主要使用 其他感官来体验 相同的 UI 例如 视力受损的人 可以使用旁白 进行导航 并使用音频 与 App 进行互动 对于没使用的人 这就是开始使用 旁白的样子 打开旁白
目前旁白只是 系统范围内的功能之一 它能将你的 UI 以这些 替代形式呈现出来 而且 由于 Toggle 和 其他控件是根据 它们的用途定义的 并且包含了 我们可解释的标签 所以它们可以自动适应这些功能
所以当我们使用旁白 导航到这个切换时会听到 加盐 开关按钮 双击以切换设置 能够显示相同的标签 即使标签不是 文本也是如此 下面关于图像 如果图像 名称不够具备描述性 你可以直接在图像旁 直接提供一个标签
当然也可以全部自定义 这的确非常激动人心 当然 即使是完全自定义的视图 你也总是可以使用辅助功能 标签修饰符直接地提供标签
现在 除了旁白 这些信息还可以 用于其他功能 比如 iOS 和 macOS 上新的语音 这样我们就可以说“Tap Include Salt” 我们的 UI 就像预期的那样执行操作
确保你的 App 是 易于使用的 意味着它能和 所有这些不同的技术兼容 意味着每个人都可以使用你的 App 而且 SwiftUI 是有一定作用的
今年有一个很棒的演讲 将会详细介绍如何 确保 SwiftUI 的 App 是完全可访问的
现在 我们已经能够快速 构建这个初始基本接口 它具备所有我们 所期望的操作 动态类型 暗色模式和辅助功能 但是我们实际上 只为吐司本身添加了 一些自定义选项
当然 大家都知道 专业的手工吐司 会有各种不同的面包类型 制作牛油果的方法 当然还有各种 酱料和辅料 要添加这些更高级 的配置选项 我们可以 从 macOS 的灵活性中 寻找一些灵感 或者我们可能想要 一个小的实用窗口让我们能 直接从我们的桌面上点吐司
你可以在这里看到 我们已经使用的 现有控件呈现了 macOS 的预期外观 即 Toggle Stepper 和按钮 但我们也有一些 额外的控件 可以让 我们选择面包的类型 添加的酱汁 以及 如何准备牛油果
这些都是 SwiftUI 中 Picker 控件的例子 Picker 是为了从 一组选项中选择一个值而设计的
Picker 显然比 其它控件复杂一些 它有三个核心属性 而不是两个
即你可以从中选择的选项 当前从这些选项中 选择的选项以及描述 Picker 总体用途的标签 现在的选择是绑定 就像属性上的 Toggle 一样 这允许我们再次将其 直接连接到 Modeler 的状态 这种绑定的类型 对应于与每个选项 相关联的标记值
当其中一个选项被选中时 该标记值将被重新 写入选择并返回到 我们的模型中 所有这些 都是自动完成的
当然 macOS 上的 Picker 并不总是显示为弹出按钮 在这个单独的窗口中 我们可以看到两种不同风格的 Picker 一个是弹出按钮 和一个 radioGroup 虽然 SwiftUI 自动 提供了一种默认样式 能够适应控件的使用位置 但是控件本身也具有 自定义样式的能力 既可以自定义系统 提供的样式 也可以自定义 构建的样式
在本例中 我们希望 重写默认样式并 强制直接使用 radioGroup 因为我们知道我们 只从两个选项中进行选择
现在我们可以考虑对 Spread 也进行同样的调整
但一开始可能只是 四种可能的简单组合 很快就能发展成 各式各样的组合 所以说到构建 Picker 我们显然不希望它 一个一个地展开这些选项 就像我们不希望 构建一个 UI 将它们 全部显示为单选按钮一样
我们已经看过使用 ForEach 构建数据驱动视图 因为每个选项都是 视图本身 我们也可以 在这里使用它 这样就好多了
下面我们将详细了解 Spread 的每一种情况 并创建一个新选项 其中 使用 Spread 的名称和其本身作为标记
现在 显然 Pickers 不仅仅 存在于 macOS 上 接下来是选择 iOS 上的 Picker 看起来 像传统的轮状 Picker 但是 由于我们正在构建 一个表单 SwiftUI 会 自动调整 Picker 以采用 这种类型的 UI 的 另一种非常常见的形式
这里我们可以看到 Spread Picker 现在 由一个导航行表示 该行同时 显示其标签和当前选定的值
轻点这一行 就会出现 一个包含所有选项的列表 轻点选择其中一个 然后就会回到上一界面
不要抢我的台词 这就是 SwiftUI 通过创建一个 简单的 Picker 来处理 和创建整个交互设计 这让我们剩下的三个 Picker 显得微不足道 就像在 macOS 中一样 我们仍然对最终样式 有明确的控制 如果我们想要一个轮状 Picker 我们可以把它加进去
现在我们有了一组很好的 App 但在我们工作或忙碌时 点吐司是一回事 而与朋友和家人 就什么是最好的 牛油果吐司进行激烈的 争论则完全是另一回事
右边的表单由我们在 其他 App 中看到的 相同内容组成 来看一下用于 构建它的代码 它使用了与我们之前 使用的相同的结构和控件 进行创建 这并不奇怪 不同之处在于 自动适应的能力 例如 使用开关按钮 而不用开关来 表示 Toggle
这就涉及到了 SwiftUI 的核心问题 你可以一次性学习一个概念 并将其应用到任何地方
SwiftUI 不仅仅是一种 一次性编写并在任何地方 运行的方法 它还是一个框架 让你能够学习这些核心概念 并在各种不同的上下文中 和平台上使用它们
这是从修饰符和 ViewBuilder 语法扩展到共享的 核心类型 如color image 和 ForEach 甚至扩展到这些高级控件 对我来说 一个真正能 说明这种知识重用的例子 是构建 contextMenu 的一个稍微特定于 平台的例子 contextMenu 本身可以 使用修饰符附加到关联视图 这个修饰语句使用 ViewBuilder 语法来定义它的菜单内容 现在 如果我们看一下菜单 我们可以看到一些熟悉的概念 有些元素在点按时 执行一个操作 并有一个描述 该操作的标签 而另一些元素则专门负责 打开和关闭 因此 内容本身去使用 我们已经学会如何 使用的相同控件来进行 进行构建 这并不奇怪 按钮 分频器和 Toggle
不过 我们的 macOS 菜单仍然会自动呈现 预期的外观和体验 从悬停和加速手势处理 到特殊的高亮 显示和选择样式
从这几个例子中 你可以 看出 SwiftUI 中的控件有点特殊 它们的定义是基于 其目的 所服务的角色 与 App 模型的连接 而不是特定于 它们的视觉外观 这意味着它们本质上 是可以跨多种历史 上下文重用的 并且可以根据上下文 平台或其他信息确定 适当的外观和体验 与此同时 它们是 可自定义的 既可以使用 视图作为标签和选项 也可以在系统 样式中任意设置这些控件 的样式 就像你在 Picker 中看到的那样 我们甚至 可以完全自定义这些样式 无论哪一种 仍然有 内置来支持辅助功能
刚才 Matt 展示了一些 使用修饰符对视图强制 执行附加行为的例子 对控件来说也是如此
iOS 上的用户可能 已经熟悉的一个 例子是改变 UI 的色调或强调色 这会影响出现不同 系统控件的数量 如果我们想把这个应用 到整个 App 中 我们可以 把 accentColor 修饰语句 应用到最外层的视图 这样它就会像这个按钮 一样被整个层级结构继承 现在 当涉及到禁用 控件时 我们可以使用 禁用修饰符 比如说在可能 没有点吐司时 禁用“Order”按钮 但是 当我们需要 禁用整个控件组时 也可能会出现这种情况 例如 当我们无法 连接到购买吐司网络 甚至无法下单时 我们会希望禁用 表单中的每个控件
但是如果我们添加额外 的控件 其实没什么意思 而且容易出错 但是正如你在一般的 修饰符中看到的那样 我们可以将这个修饰符 移除再应用到整个表单中 就像我们在使用 accentColor 修饰符时所做的那样
现在我们表单中的所有 控件都将基于这条语法禁用
所有这些适应性 和继承操作都非常 强大 令人惊叹 因为我们使用的是 这些简单的值类型视图 但让我们来看看 hood 的工作原理 这些例子是建立在 environment 之上的
environment 由视图 出现的所有上下文组成
这些都是大家以前 可能认为是共享 全局状态的东西 是你的视图上的 集合或属性的一部分 或者需要访问之前的 历史才能取出值
但是现在这些都被打包 到 environment 中 任何想要访问它的人 都可以对其进行访问 每个视图都继承 其原有的 environment
现在举个例子 当在 阿拉伯语环境中运行时 我们的 App 的根本环境 有一个从右到左的布局方向 每个视图都继承这个布局方向 但是在任何给定的位点 都可以为视图的 子树重写环境
如果我们要创建 媒体播放控制 我们想要确保它们是 从左到右排列的 通过使用环境修饰语句 我们可以把它添加到层级结构上
现在 environment 也是使预览功能如此强大 的重要技术之一 它能够在各种不同的 上下文中显示相同的 UI 因此我们可以根据人们 可能使用它们的所有方式 来预览我们的 App
现在各位已经了解了 环境是如何自动影响 各种系统视图的 并且自定义视图也能 使用 environment 所以我一直在为我们的 下一个更新做一个小控件 它能选择鸡蛋放在 吐司上的确切位置 大家可以看到它是使用两个 简单的 ZStack 图像构建的 底部的 Toast 和顶部 带有 dragGesture 的定位图像 这样 我们就可以轻点 并拖动鸡蛋到正确的位置
现在如果我们要使用 Egg 视图 可能在某些情况下 就需要禁用它 也许商店的鸡蛋卖完了
但由于我们使用的是系统 dragGesture 它会被 禁用的修饰符自动禁用 所以如果有人想 拖动这个鸡蛋是不会成功的
当然 我们还应该 提供一些视觉反馈 来说明它也被禁用了 还好这个操作很简单
我们可以添加一个 环境属性 该属性 会连接到环境中的 isEnabled值 我们可以像使用其他属性 一样使用它的值 例如 当它被禁用时 降低我们整个 结构的饱和度
如果放置鸡蛋的视图不再被禁用 SwiftUI 将自动 恢复视图的主体并将其 重新呈现为未禁用状态 同样 这是 SwiftUI 在自动管理我们 对环境的依赖关系 这样我们就可以表达 视图与环境之间的关系 而不必担心什么 时候会发生变化
目前我们已经介绍了 一些控件以及将它们组合 在一起的方法 但我们仍然遗漏了 每个 App 中非常 重要的部分 那就是 在这些屏幕之间导航 从订单表单到鸡蛋放置的 Picker 再到订单历史记录 现在让我们从订单开始讲起 有些人可能已经 注意到了表单中 标题的外观 它不使用标准的 导航栏样式 因此我们可以首先将 OrderForm 包装在 NavigationView 中作为 App 的内容 NavigationView 可以 在 App 屏幕中导航 显示更多嵌套 或详细信息 在 iOS 系统中 NavigationView 还添加了标准的导航栏 Chrome 然后我们可以使用 NavigationBarTitle 修饰语句 来为表单生成一个 漂亮的标题 这个修饰语句有点特殊 它提供了能够被 源始 NavigationView 解释的信息
我们之前看到过 一些修饰符的例子 它们用环境使信息沿着 视图层级架构向下流动 这是一个使用 首选项向上流动信息的例子 现在我们不打算对此 进行过多的详细讨论 但是稍后你会看到 其他类似的例子
关注一下表单 我们要做的下一件事 是添加支持点鸡蛋的功能 我们可以在这里 添加一个 Toggle 当有人选择加鸡蛋时 我们可以添加一个导航行 它会转向 EggLocationPicker 我们把它展开看看 它是如何工作的
它是使用一个绑定到 我们订单是否包含一个 鸡蛋的切换来构建的 然后它使用与 Matt 之前展示的相同的 ViewBuilder 条件 来选择性地包含导航行 现在 最酷的事情是 我们为 Toggle 提供了 一个动画绑定 所以每当有人轻点 这个开关 我们的导航行 就会被动态地 插入到 FormList 中
表示导航行也非常简单 它使用一个名为 Navigationbutton 的专门控件 允许我们在交互时 提供一些目标内容来导航 Navigationbutton 自动 提供了所有出色的外观 和体验 比如尾部边缘的 显示指示器 由于视图是轻量级的 我们不必担心 在这里创建了 EggLocationPicker SwiftUI 只在 实际呈现这些视图时 才会呈现它们
现在在 EggLocationPicker 中 我们可以使用 PlacementView 自定义导航栏 这样一旦它被显示 标题就会反映它的当前状态 我们还可以添加一个追踪的 BarItem 来快速地将 egg 重置回初始状态 正如大家所希望的那样 这里的项与我们 已经学会如何使用 的视图是相同的 因此我们 只需要提供一个按钮
这就是创建完整导航 体验所需要的全部
现在我们可以把注意力 转向 OrderHistory 现在我们想导航到这里 但它不是表单的更详细 或嵌套的信息 而是 App 的一个 完全不同的部分
这更适合使用 TabbedView
因此 我们可以像处理 NavigationView 一样 将表单封装在 TabbedView 中 然后将 OrderHistory 添加为另一个子元素
两者都有 tabItemLabel 修饰符 它负责向 TabbedView 描述如何在选项卡栏中标记它们
现在我们可以快速跳转到 OrderHistory 但是现在我们已经对 OrderHistory 有了一个简单的细节了解 我们可能想要将其 扩展为更详细的信息集 以便从历史列表 导航到这些信息 这是嵌套或显示 更详细信息的另一种情况 就像我们前面在 NavigationView 和按钮 中看到的那样 因此我们可以替换 OrderHistory 列表的内容 因此我们可以使用这个 新的 OrderDetail 作为 NavigationButtons 的目标 而不是将它以内联 方式显示在列表中 构建一个数据驱动 列表非常简单 它可以导航到其他内容
这在 iPhone 上 很好用 但如果是 iPad 我们希望这个设置 使用主细节和 SplitView
不像 iPhone 上的 navigationStack 会推送到单个 RootView 在这里我们知道 我们有 RootView 的 两个导航 即能够将内容 推送到细节的 Master 因此 虽然我们的 NavigationView 仅对 iPhone 上的单个 RootContent 执行 正确操作 但我们希望 指出它本质上 包含这两部分内容 OrderHistory Master 和 DetailView 这里 我们可以使用 OrderDetailPlaceholder 视图 作为占位符 以便在没有选择 任何内容时充当占位符 现在 当在 OrderHistory 中与 Navigationbutton 交互时它将自动被 推送到 OrderDetail 这将像我们在 iPad 和其他使用 SplitView的 大类上所期望的那样 而对于小尺寸类 则会自动折叠成 单个 NavigationStack 当然 这在 macOS 上也能操作 创建出一个 SplitView 这不是写一次 就能运行的 还需要 一些额外的设计考虑 比如 macOS 上 信息密度的增加
但是 SwiftUI 会自动 处理平台的底层外观 从 SplitView 的操作到表行 的高度等等
这样我们就可以学习 如何使用这些不同的概念 后把它们应用到任意地方
然后我们可以把时间集中在 一些令人兴奋的自定义功能上 这些功能会使你的每个 App 都变得很棒
在这最后的一个小时里 我们已经讲了相当多的内容 还有一些其他的讲座 会展现更多的细节 我们展示了状态和绑定 将如何改变你和控件 的交互方式 但是 SwiftUI 中的数据流将使你 重新考虑数据驱动 的 UI 更新
我们使用布局调整器 建立了一些自定义视图 但是 SwiftUI 中的自定义控件 将深入研究布局 图形 和动画的高级应用 并做一个出色的演示
我们知道 很多人会 迫不及待地使用 SwiftUI 并且可能想知道 是否可以将其集成到 现有的 App 中 好的方面是 SwiftUI 的设计可以与 现有的视图和 模型无缝融合 我们有一个完整的讲座 向大家展示如何做到这一点
我们谈到了 SwiftUI 是如何设计的 来让你的 App 能让所有人都可以访问 当然 总会有一些 额外的考量 这次演讲 将会涉及更多的细节 最后 但最重要的是 我们展示了 SwiftUI 是如何提高跨平台 共享的门槛的 所有设备上的 SwiftUI 将这一点作为基准 并详细介绍了如何在任意 平台上开发出优秀的 App 还有一些额外的演讲 比如驱动 watchOS 和《What's New in Swift》 中关于 watchOS 的详细内容 最后 感谢大家的收看 我们对此十分感激 [掌声]
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。