大多数浏览器和
Developer App 均支持流媒体播放。
-
介绍 SwiftUI
了解声明式编程:我们将会介绍编写声明性代码的好处以及如何结合 SwiftUI 和 Xcode 帮助你更快地构建出色的 app。与此同时,你还能了解如何从头开始构建功能全面的 SwiftUI app。
资源
相关视频
WWDC23
WWDC20
WWDC19
-
下载
(你好 WWDC 2020) 大家好 欢迎来到全球开发者大会
大家好 我是 Jacob 之后 Kyle 会加入进来 我们很高兴可以向你展示 SwiftUI 这是一个快速构建优秀 app 的好方式 我们认为学习 SwiftUI 的最好方式 是通过构建一个 app 来了解它的操作过程 使用 SwiftUI 感觉像是变魔术 但事先声明 我的袖子里可什么都没有 我想从头开始介绍 在 SwiftUI 中创建一个 app 的 整个过程 我们要创建一个什么样的 app 呢? 我来为各位讲一下背景 Kyle 和我喜欢吃三明治 所以我们一直在整理一个 我们发现的最好吃的三明治列表 然后我们想为此创建一个 app 下面我们跳转到 Xcode 开始创建 我从创建一个新项目开始
并且使用跨平台 app 模板
然后我将它命名为“三明治”
这就是我们的新项目 Xcode 已经为我们提供了 iOS 和 macOS 版本中 我们的 app 所需要的一切 我们有为 iOS 专用资产创建的组 也有专为 macOS 创建的
但是我们大多数代码都在共享组中 在这里有一个文件夹 包含了我们 app 的所有代码 而这似乎太短了 以至于无法成为 设置我们整个 app 的代码 但是我们有这些就够了 我们稍后再回到此代码处 现在 我们从 app 视图的代码开始
(“你好 世界!”) 编辑器的左侧显示我们的代码 右侧显示该代码的视觉呈现的 canvas 在 SwiftUI 中 视图定义只需要 Swift 代码 这意味着 canvas 和代码编辑器 不过是查看和编辑同一代码的不同方式 如果我们在 canvas 中 选择某个东西 这一选择也会反映在代码中 如果你在代码中更改什么… (“我的三明治”) 该更改也会反映在 canvas 中 他们之间无缝协作 这样你就可以随时在它们之间转换 我再深入分享一下它是如何工作的 canvas 向我们显示视图代码的预览效果 而且它还能帮助我们编辑和了解该代码 Xcode 通过编译我们的真实代码 并运行该代码 来生成一个结果显示这些预览 但是关于预览我最喜欢的一点 是它们也是使用 SwiftUI 代码创建的 稍后 我们将看到这一特点 如何赋予我们能力来自定义预览 现在 我们的 app 要显示三明治的列表 下面我们先来为列表制作单元 我将在这段文本下添加另一段文本 以显示有关每个三明治的更多信息 而且我要从资料库中进行添加… (三明治 编辑)
我只需将其拖到 canvas 上即可
我将其拖动到不同位置时 Xcode 还会向我展示不同的情况 我放下它的时候 预览会更新来显示添加的文本 更好的一点是 Xcode 已编辑了我的代码 它自动添加了那个文本 (“占位符”) Xcode 已将这些视图嵌入到 VStack 中 以获取我想要的布局 一个 VStack 或者一个垂直叠放 是 SwiftUI 中常见的布局容器之一 它能让你垂直叠放视图 还有一个 HStack 可让你水平叠放视图
这些叠放就是容器 我可以在里面放置任何我想要的视图
我要用三明治的配料代替这个占位符 现在 我们就使用硬编码值 (“三种配料”) 下面 我们在文本旁边添加一幅图 我可以像在编辑器中一样 轻松地在代码中进行编辑 我们将视图嵌入到这里的 HStack 中 我将按住 command 在视图上点击 然后选择“嵌入到 HStack 中“
Xcode 已经添加了代码来做到这一点
现在我可以在 VStack 前面添加一幅图像
(图像) 一会儿我们再添加一些资产 现在我将使用 SF 符号图片 来做好准备并开始运行 (“照片”) 现在我们有了单元格的基本版本 我们来设计一下单元格 我可以在一个视图上按住 command 点击 并对其进行检查 以查看有关该视图的一些属性
我们更改一下 VStack 的对齐方式
Xcode 已经更新了我的代码来显示该值
现在我们来检查一下配料的文本 这一次 我们使用 canvas
专家提示:你可按住 control 加 option 并点击 直接跳转到 inspector
我要更改为一个较小的字体
我们使用“副标题”吧
Xcode 编辑代码的一大好处是 这样可以帮助我学习如何使用 SwiftUI 现在我可以看到代码 为一些文本设置了字体 我们把这类方法称之为“修改器” 在 SwiftUI 中 它们被用来自定义视图的外观或行为 我将在代码中添加另一个 修改器 将前景颜色设置为合成色
我现在有了单元格 我们把它放到一个列表内 为此 我要按住 command 点击单元格 选择“嵌入到列表中”
这样会把我的单元格打包成列表 并对这个单元格进行五次迭代 这个编码就可以显示一个列表了 没有代表或数据源 只有列表内的视图
下面 我们将其连接到一些数据 我要拖入一些资产 和我先前创建的模型文件 (文件)
我的模型里有一些我们将使用的信息字段 要在 SwiftUI 的列表中使用它 我只需要使这种类型可识别即可
这样可以让列表知道何时有新项目进出 我们已经有了一个 ID 属性 这正是我们所需要的
该模型文件夹还包含一些测试数据 可以用来调试我的 app
现在 我们回到视图页 上传我们的数据
我要在视图中为三明治添加一个属性
预览的优点之一 是它们可以使用自己的测试数据 所以我只在此处 上传我们的测试数据即可
你可能注意到了我们预览上方有一个扳手
当我对类型进行较大的更改时 例如添加三明治的属性时 Xcode 会暂停预览 直到我更改好能够继续更新为止 我可以点击此按钮 或者按 command-option-P 继续
下面 我们用自己的数据来驱动列表
我们将三明治传到列表…
然后我们要更新文本 来显示三明治的名称
然后显示正确的配料数
现在我们有了真正的图片 图片我们就使用三明治的缩略图
也许你已经注意到了 我们的单元格中有了细微变化 我们开始的时候 它们是标准44磅高 但是当我们换成更大的图片时 单元格自动延展了高度 来确保这些图片可以被容纳进去 而不增添额外的工作
现在这一情景里 我有了这些图片 他们看起来很清晰 我们使用另一个修改器 将圆角半径应用到图片上
如果你不确定可以使用哪些修改器 可以在这里的 Xcode 资料库中 查看和选择修改器列表
我们可以找到圆角半径修改器 并将其拖动到 canvas 里
请注意 对于在我们单元格列表中的视图 Xcode 甚至知道这些单元格有相同的定义 所以这个修改器将应用到所有的单元格上
我们把它拖到我们的图片上…
然后调整数值
我们的单元格和列表看起来很不错了 下面我们需要做的是能够在单元格上轻点 来查看有关三明治的更多详细信息
为此 我们将列表 打包到 NavigationView 中
浏览视图可以让你 app 的浏览操作 在不同部分之间进行 在 iPhone 里 会显示一个浏览栏 还会允许推送到浏览叠放里 我们再为视图设置浏览的标题 以在栏中显示“三明治” (三明治) 然后 我将设置好单元格以推送到叠放上 为此 我们可以把单元的内容 打包到 NavigationLink 里
浏览链接需要目的地来推送 现在 我们要使用一些文本 来显示三明治的名称
然后把我们的单元格作为内容 放到浏览链接里
你会发现用户界面已经自动更新好了 这样就能显示所有单元格内的详细内容
SwiftUI 会自动处理这一类的细节 这样我们的用户界面 在默认情况下 看起来是没有问题的 我们再检查一下 以保证单元格正确运行 这时候使用预览也是很棒的 我可以在预览上点击“播放”按钮 我可以直接看到效果
这使我可以在 canvas 与我的真实代码进行交互
我可以点击这些单元格 以确保它们按预期方式滑进并弹出
如果我滑动窗口 你会发现 SwiftUI 已自动 为我更新好了高级行为 我们只滑动页面的时候 无需额外编译 我们的单元就格保持在标亮 与交互式标亮消失状态
我最后想对该列表进行的更改 是在列表中连续显示三明治的数量 但是我们的视图代码现在有点太长了 我不希望保留这么大的单独视图 所以我们把单元格分离出来 使之形成独自的视图 好把一些关注点分离
Xcode 只需一个简单操作 就能帮我实现这一点 我只要按住 command 点击 我想分离的视图…
然后选择“提取子视图”
所有的视图代码都移动到了这个新视图中 我还可以给它一个名称 我们命名为“SandwichCell”
然后我给三明治添加一个属性…
然后把该三明治传进去
这极大地改进工作流程 在 SwiftUI 中 视图非常简洁 所以你不必担心需要创建额外的视图 来更好地压缩或分离你的逻辑
既然我们的列表代码已得到精简 我们来添加三明治数量的这一行
现在 我们要使用一个集合 来驱动我们整个列表 这对于纯粹由数据驱动的列表而言 是非常棒的
但是如果我需要的内容更多 我还可以用 SwiftUI 来混合列表和其他容器中的 静态和动态内容 我可以用“ForEach”来代替推送集合 传送到列表上
这样可在集合内为每个条目创建一个视图
我可以在这个数据驱动的元素旁边 添加一个静态元素 我要在这个“ForEach”下再添加一个文本
让它来显示三明治的数量
同时也把前景颜色修改为合成色
我们再居中显示文本 为此 我们可以将文本嵌入到 HStack 中…
并且添加分隔符
分隔符是 SwiftUI 中常见的布局元素 它的功能像工具栏中的灵活空间一样 可以扩展以填充任何可用空间 所以这两个分隔符可以分开任何可用空间 从而使文本居中
下面我们来构建详细视图 我要使用 SwiftUI 视图模板
创建一个新的视图…
命名为“SandwichDetail”
Xcode 会自动给我提供一个视图结构 和预览代码来帮助我创建新视图
我想让该详细视图 显示有关我三明治的更多信息 所以我要把它输入进去
和之前一样 我使用预览代码 来设置这个视图的版本 该视图使用我们的测试数据
为了创建我们的视图 我用带有三明治图片名称的 图片就行
现在图片显示出来了 但是从视图效果看 是太大了 默认情况下 SwiftUI 以图片的真实大小 来显示所有图片 以防止人为视觉效果放大或缩小图片 但对于像这样的照片 我们希望能够缩小它的尺寸 我们可以使用图片专用的 调整大小的修改器来具体操作
现在图片的尺寸与我们屏幕大小一样 但我很想保持图片的原始高宽比 我可以用另一个修改器 来设置高宽比
这时我要在“铺满” 和“合适大小”之间进行选择 “铺满”可扩大图片以占据整个框架
而“合适大小”
可以确保图片能在画面内完整呈现 预览可以让我很容易地看到和明白 这两者之间的不同 现在我们来使用“合适大小” 这样我们就可以看到完整的图片
我们回到列表处 更新我们的单元格 以便我们点击时 可以滑动出新的详细视图
我们创建 SandwichDetail…
并且把当前的三明治传输到这里
我把预览切换回实时模式 现在我可以点击单元格查看我的图片
但是现在我在预览图片 可以看到我忘记 在浏览栏中设置它的标题了 我们现在回到详细视图中解决这个问题
我只需在这里添加 相同的 navigationTitle 把我的标题设置为 sandwich.name
但是在我右边的预览中 我们只能看到视图 而我很想能够快速地验证我的修改 因为 SwiftUI 视图可用的强大功能 预览里面都具备 所以我的想法就能实现了 我可以把自己的预览 设置在 NavigationView 下 就像我在 SwiftUI 代码的 任何地方可以设置的一样
现在我视图的预览中有一个浏览栏 我可以在这里看到我的标题 (俱乐部)
我挑选好吃的三明治时 对于我来说 有一件事非常重要 三明治必须添加适量的调味酱 没有调味酱 三明治会太干 调味酱过多 会把三明治弄得乱七八糟 我可以看到三明治上面有调味酱 但我想确保没有加太多 如果有一个“铺满”的特殊高宽比…
我就可以看到三明治的细节 这个看起来不错
我们真正想要做到的是 在“铺满”和”合适大小”之间来回切换 以便在查看细节 和在查看完整图片间切换 但是我如何在 app 运行时 动态更改这个高宽比的 内容模式呢? 为了理解如何实现这一点 我们需要在 SwiftUI 中 了解更多视图运行的方式 和原因 下面这一部分就交给 Kyle 讲解 谢谢你 Jacob 你们好 我是 SwiftUI 团队的成员 Kyle SwiftUI 或许不同于 你之前习惯使用的工具 所以 在我们深入讲解之前 我们先回顾一下 花一些时间了解视图的工作方式 这是我们刚才执行的三明治的详细视图 (视图简洁) 请注意 在 SwiftUI 中 视图是符合视图协议的结构 而不是继承于用户界面视图等 基类的类 这意味着你的视图不继承任何存储属性 它在叠放上分配 并按值传递
三明治细节仅存储一个三明治 所以它是一个三明治的大小和重量 没有额外的分配或引用计数
在背后的运算中 SwiftUI 大刀阔斧地 把你的视图层次瓦解成 一个有效的数据结构以进行渲染 正因如此 我们在 SwiftUI 中 自由使用了小型的、单一目的的视图 你也应该如此
从这一部分中 我想让你了解的是 在 SwiftUI 中 视图非常简洁 正如 Jacob 在前面提到的 永远不要犹豫重构你的 SwiftUI 代码 因为提取子视图几乎不占系统开销 SwiftUI 中的视图 和传统用户界面框架中的视图 主要作用是相同的 他们定义用户界面 视图协议只需要一个属性:主体 这本身就是一个视图 (视图是组合而成的) 通过组合较小的视图 你可以构建较大的视图 通过组合图片 处于原生分辨率的图片视图… 可调整大小 在任一维度上伸缩图片的视图…
和高宽比 按比例伸缩其子视图的视图…
我们可以 创建三明治的详细视图
你可能构建的任何视图的渲染 像三明治详细视图 只是其主体的渲染
如果你在执行主体时设置了一个断点 并且调试器在断点处会停止 这意味着 框架已经决定 它需要你的视图新进行一次渲染
嗒哒! 框架知道何时获取新的渲染 因为除了定义用户界面 视图还定义了它的依赖关系
我们来扩展一下三明治的细节 以便用户可以点击 在合适大小和铺满屏幕间进行切换
(视图是动态的) 我们需要的第一个东西是状态变量 来显示图片尺寸是否发生了变化
当 SwiftUI 看到带有状态变量的视图时 它会代表视图给该变量分配 持久存储
如果我们决定根据该状态变量 来选择铺满屏幕或合适大小 那么我们得到的视图 它在放大时 渲染是这样 而在没有放大时 渲染是这样
现在我们只需一个手势 即点击 就可以在这两种状态之间切换 点击的时候 图片就会放大到铺满屏幕…
然后缩小到合适大小
那当我们点击时 这里实际上发生了什么呢?
状态变量的特殊属性之一 是 SwiftUI 可以知道 它们何时读取和编写 因为 SwiftUI 知道 这里的缩放是在主体中读取 它知道视图的渲染依赖于它 这意味着…
变量改变时 框架将再次请求 获取主体 最终使用新的状态值 以便可以更新渲染
但这次会使用不同的内容模式 传统用户界面框架无法区分状态变量 和简单的旧属性 但是 我发现这其中的区别非常难说清楚 在 SwiftUI 中 你的用户界面可能会发现 它处于以下每种可能的状态
滚动视图出现位置偏移 按钮在标亮显示 浏览叠放中的内容…
这都衍生于我们通常称为 “信息源”的权威性数据
你的状态变量和模型共同 构成了你整个 app 的信息源 (信息源在哪里?) 我前面提到过 高宽比的调用可形成视图
其定义大概是这样的 即内容模式是简单的旧 Swift 属性
你可以将每个属性有条理地 分类成信息源或者是衍生值
缩放后的状态变量就是信息源
内容模式属性是从它衍生而来的
我们回想一下 SwiftUI 可以知道 状态变量何时读取和编写 所以当某处变化时 它会知道该更新哪个渲染
框架通过请求新的主体 从头开始制作新的高宽比视图 来更新渲染 从而推翻内容模式和任何其他存储属性
这一机制使得在 SwiftUI 中 所有的衍生值 能保持最新状态 (数据流原语) 我们已经看到 每个状态变量都是读写的数据之源…
而每个简单的旧属性都是只读的衍生值 在本次演讲中 我们不会举例 但是 SwiftUI 发明了一种 称为“binding”的工具 用于传递读写衍生值 严格来说 任何常数都可以 作为一个非常好的只读信息源 驱动我们预览的测试数据 就是这方面的一个例子
最后 我前面曾提到 你的状态变量和模型 共同构成了整个 app 的 信息源 稍后 我们会看到 Jacob 使用可观察对象 来教 SwiftUI 如何观察模型对象中的变化
如果你还是不清楚这些原语之间的区别 也无需担心
我们有一个讲座 专门培养你的直觉 让你了解什么时候该使用 哪一个数据流原语 好 我们来回顾一下前面的内容 我们所看到的 与你在传统用户界面的框架中 所做的确实不相同 就传统来说 视图本身是会不变的 你需要尽最大努力使它们保持最新状态 并且保持一致
当你使用传统用户界面的框架时 你可能不会考虑这方面的事 但是视图每次读取数据的时候 都会创建一个隐性的依赖关系 这是一种依赖关系 因为当数据改变时 视图需要更新以反映新的值
而当它失败时 说明有漏洞 SwiftUI 会代表你自动管理依赖关系 重新计算适当的衍生值 好让这种事情不会再次发生 当然 我们一次不仅仅管理一个依赖关系
我们工作的用户界面既庞大又复杂 当谈到 你必须在大脑中记住多少内容 以及有多容易犯错的时候 你就会发现我们现在 手动管理依赖关系的方式 真的是非常困难的方式 尽管我已尽了全力 但是我发行的每个 app 每次在更新的时候 都会出现用户界面漏洞 这些线路中的每一个都是一个依赖关系
而且即使你了解了所有这些关系 你仍然必须要确保你的用户界面 在事件处理程序回调的所有可能的排序中 处于一致状态
为了阐明我这句话的意思 我们来看一下 在 UIKit 中执行的三明治 app 旧版本中的一个漏洞 这是视图控制器代码的草图 你放大时 它有一个时髦的增强按钮
如果 Jacob 最终得到了 像这样的低分辨率图片 他仍然可以验证 三明治中是否含有健康分量的调味酱
轻按下按钮将会在后台线程上 调动机器学习操作以便增强图片
这样好多了 我想我看到了辣芥末酱 只不过有一个问题
我们有一份偏离任务指示器的报告 该指示器从不停止旋转
这个漏洞是由事件的意外排序引起的
当你直接在事件处理程序回调中 转换子视图 而不是更新信息源 并从中得到你的用户界面的时候 很容易造成这些错误
这是因为我们会忍不住编写已经想到的 满意路径 而直接忽视那些不满意的路径
问题是 随着事件数量的增加 不满意的路径数量就会激增 假设我们有四个事件 可能会出现多少不同的排序呢? 实际上有24个不同的排序 任何四个事件处理程序都可能被调取
在实际情况中 情况甚至比这更糟糕 因为每个事件都可能会多次地发生 假如 举个例子 一名用户正在按增强按钮 任何尝试过处理异步回调的人 或者执行可中断动画的人 都应该对处理这种复杂性的挑战不陌生 这些集合处理程序 可能会在各种意想不到的时间爆发
如果我可以告诉五年前的自己 一件关于工作上的事情 我会说用户界面编程很困难
没有人会假装同步多线程代码是件易事 在我编写的一些多线程代码中 我花几个月的时间才能找到并解决漏洞 即使是这样 对于它的正确性 我也没有 100% 的信心 而实际上 很多用户界面代码就是这样 我觉得因为它通常只表现为视图丢失 或者放置位置错误 对于其困难程度 我们有些轻描淡写 但我们不应该这样 竞态条件和用户界面的不一致性 有着相同的潜在复杂性来源 这些容易忽略的排序… 对于我们编写的很多视图 它们要处理的事件远超过四个 模型通知、目标操作、委托方法 生命周期检查点 集合处理程序… 这些都是要处理的事件 有12个事件的视图 将大致等于12个因数的可能排序 结果差不多有五亿 你可以把这个想象成大脑的大O符号 (复杂性 事件数量) 你是人类 你的大脑中每次只能装下有限的信息 (人类大脑的能力) 这条虚线? 它代表你的 app
你觉得这两点之间的差值是什么? 没错 是漏洞
随着我们功能的添加 可能的排序情况会激增 而我们忽略漏洞的情况发展到一定地步 会达到不可避免的程度
我想你们很多人已经发现 在使用传统的用户界面框架时 将你所有的视图更新 收集到一个单一方法内 会带来有一种简单性 如果你要这么做 就搞定了我们刚看到的曲线最难部分 因为当只有一种方法时 只会有一个可能的顺序被调入
你可能没这样想过 但这个模式迫使你为用户界面 所处于的每一个可能状态 定义一个信息源 并从总的信息源中衍生出视图的属性
如果你觉得听起来熟悉 那是因为 SwiftUI 直接受到了最佳实践的启发 通过将主体 作为惟一调用的入口点 我们在框架中对其进行了编码
这样一来 我们解决了一些棘手的案例 比如使用传统用户界面框架时 始终无法适应这种模式 至少我是这样 比如删除子视图 推送到浏览叠放 对列表视图执行更新等
这就是为什么视图还有 app 和情景 以及任何其他带主体的 SwiftUI 抽象部分 按照它们的方式运作 因为你只是人类 这种简单地为用户界面部分 获取新实例的模式 随大脑改变比例 实际上消除了用户界面的不一致性 接下来我们回到演示环节 完成三明治详细视图 Jacob? 谢谢你 Kyle 为了更仔细地观察我们的三明治 我们来添加一个状态属性…
称为“缩放”… 并默认为“否”
状态只能在视图的执行内访问 所以我们把它设为 private
然后我们会在高宽比的内容模式下使用它 以在我们放大的“铺满” 和不放大的“合适大小”之间切换
最后 我们会添加一个轻点手势 以切换缩放的状态
我们在实时预览中试一下
我们现在可以在这些模式间进行切换
但你可能注意到了 当我们放大时 底部会有一些空白 SwiftUI 会自动将你的视图 放置在我们所谓的安全区域 这意味着你 app 中的用户界面元素 不会被圆角半径剪切
但对于这样的边到边图片 我们其实想扩展到整个屏幕
为此 我们可以添加一个修改器…
以忽略安全区域 (忽略安全区域的边缘)
具体来说 我们会在底部边缘忽略它
好 我们就快好了 但这里少了点东西
这里需要一个动画 借助 SwiftUI 添加动画很容易 我可以把改动打包到 withAnimation…
现在它可以在不同的状态下展现动画
不仅如此 该动画完全可交互并可中断 我随时可以点击 它总能正确展现动画
接下来我会添加增强按钮 但结果是 Kyle 训练模型的方式 只对他展示的那张图片起作用 所以我要添加更加有用的东西 Kyle 喜欢像这样的辣三明治
但我不喜欢 所以我希望有一种方法 可以快速知道 一个三明治是辣的还是不辣的
对此 我们可以在详细视图下 显示一个指示器 (熏牛肉配黑面包) 我会围绕我们现有的三明治图片 添加一个 VStack
我会将更加通用的修改器 应用到该 VStack
我要在这儿展示一个图片和文本 实现这一操作的好方法是使用标签
标签需要一个名称来显示… 我们使用“辣”
它还有一个相关的图标 我们使用系统图片…
称为“flame.fill”
标签为我们共同显示了图标和标题 它也可以被用于其他场合 比如列表和菜单 它会自动呈现正确的外观 间距和大小
我想在这里放一个页底横幅 它位于屏幕的底部 后面有一个背景 要把它移到底部 我们只需添加一个分隔符…
这样会将横幅移到底部 将图片移到顶部
为了让我们的图片居中 我们在图片上方再添加另一个分隔符
分隔符会自动采用最小尺寸 来保持元素间的一些边距 但在这个案例中 我们希望图片能够 移动到其容器的边缘 我们来把它们的 minLength 设为零
我们还要给主题添加些边距 这样看见它时仍有一些间隔 而且并不会碰到屏幕的边缘
在 inspector 中…
我可以直接点击这个按钮 为这个视图增加边距
这样好多了 我们把字体也调大
我们注意到 不仅文本大小增加了 符号图片也增大了 符号图片自动使用和文本同样的字体信息 以适当调整自身大小 我们使用标题字体
为了让这个屏幕看起来有辣的感觉 我们给它弄个红色背景
使用背景修改器 我能够将任何视图 放置于要应用到的视图的后面 这些通常与单色一起使用 以给视图一个单色的背景
我们的视图后面有红色填充了 但为什么只是这个小区域有呢?
在 SwiftUI 中 视图会调整自身大小以适应其内容 所以在这个情况中 图片和文本是它们的自然大小 而且我们所应用的边距还设置了间隔 就像之前说的 通过添加的分隔符 和一个 HStack 我们可以做到边到边扩展
接下来是稍作收尾的工作 我们把前景颜色变成黄色 以匹配我们辣的主题…
然后更新我们的字体为小体大写
看起来不错 现在我们有了横幅 但我们只希望它在三明治是辣的时候出现 我们怎么做呢?
我们使用的声明式语法使其非常简单 我们可以只用一个“if”
我们查看一下我们的三明治是否是辣的…
如果是辣的 会显示我们的主题
为了检查这个 我们可以改变预览数据 以显示不一样的三明治 也就是不辣的
但更厉害的是 我们可以设置预览以显示视图的多个版本 我可以点击加号按钮 以添加这个预览的另一个副本
我会更新我们使用的数据…
以显示不同的三明治 现在我们可以看到 视图中含有“辣的”横幅的版本 以及一个没有辣的版本
这样一来 我们在编辑时可以确保 两个版本的视图 都会按我们希望的方式运行
请注意 Xcode 添加另一个预览的方式 只是通过添加视图的另一个实例化
我喜欢这个横幅 但当我们放大时 我不希望它 占用到三明治图片的空间 所以当缩放时 我们就把图片隐藏 我们可以通过更新条件实现
现在我们缩放时 主题就会显示、隐藏
它甚至会展现动画…
淡入、淡出
我们还可以通过设置不同的过渡 自定义动画行为
我们在底部边缘使用“.move”
现在它就能滑出、滑入了
请注意 当动画还在播放时 我点击…
可以注意到图片的尺寸又变回来了 不管我做什么 一切都保持互动状态 并且它总是在合适大小的地方结束 这就是我们的详细视图 我们来回顾一下我们刚刚创建的东西
我们的详细视图配置了要显示的三明治 记住 这是此视图的父级传入的衍生值
对于是否缩放 我们都保持状态属性 这是由框架来维持的 并控制了高宽比的内容模式
这样 我们就有了横幅
只针对辣三明治才可看见 而且仅在未被放大时可见
我们还指定了一个过渡 以使之滑入、滑出 过渡期间实际发生了什么? 当它被移除后 视图会动画到屏幕外的一个新位置… SwiftUI 会一直等到动画完成 从而真正从层次结构中移除视图 当它返回时 SwiftUI 会把它插入屏幕外 然后用动画把它移回去
能够如此轻松地使用动画 从层次结构中添加和移除视图 令人感到非常惊奇
记住 这个动画始终可直接交互
与事件驱动相比较的话 这就是数据驱动真正的闪光点 Kyle 谈到的所有这些事件 在动画的时候也都能发生 动画的开始和结束是更多的事件 在事件驱动的环境下 要创建这样的东西 极其困难 但在 SwiftUI 里 只是一行代码的事
接下来我们回到三明治列表 完成这个 app
我们开始时使用了多平台 app 模板 但到目前为止 我们只关注 iPhone 要让这个 app 在其他平台运行 我们还需要做多少工作? 我们来看一下 我要将运行目的地从 iPhone 转换到 iPad 上…
然后启动
SwiftUI 已将浏览视图 转换成了分屏视图 所以我可以在左边选择三明治… 并在右边显示它们 (火腿蛋热三明治)
我在预览中注意到的一件事是 如果我没有选择好的三明治 我就会看到一片空白区域
我要改善这点以显示一个占位符 说:“请选择一个三明治” 为此我们只需要在浏览视图里 添加一个第二视图
就像你可以添加多个视图到叠放处一样 你可以在这里添加多个视图
(“请选择一个三明治”)
但这些视图并未被叠放 而是分配到了浏览视图 以最合适的方式显示出来 在这个情况下 第一个视图显示在左边 第二个视图成为右边视图的占位符
而在 iPhone 上 占位符会被自动删除 因为我们不需要它
我们来看看在 macOS 上的情形
在这里一样运行良好 我们让同样的占位符显示在这里 就像在 iPad 上一样
在 Apple 所有平台间 我们能够使用同样的视图代码 模型代码和 app 代码 我们可以针对特定平台进行改进 就像这个占位符一样 以求更好的效果
随着时间推移 我们需要能够改变三明治列表 我们来添加一些编辑支持 我们在这样做时 还要让我们的数据模型 更加真实一点 现在 我们 app 里的数据是完全静态的 我们现在有这么多三明治…
不管我们从什么开始 我们都会一直拥有 我们来更新模型以拥有根存储对象 这样会包含我们的三明治 并会随时间推移能够改变
我要从我们的三明治商店拖进一个 预建好的模型文件
请注意 这是数据存储中的商店 不是卖三明治的地方
请注意 我们的商店 是包含三明治的可变对象
我们还有一个供测试的商店的单例实例 我们现在要做的就是 告诉 SwiftUI 我们的对象何时改变
为此 我要让它符合 ObservableObject 协议
然后我可以用 @Published 标记任何我想观察的属性
那我们怎么使用新模型?
就像我们使用 @State 为一个值创造信息源一样…
我们可以使用 @StateObject 为可变对象创造信息源
StateObject 会自动观察对象 以在其改变时更新视图 我们可以把 StateObject 添加进这里的视图代码里 但因为这是我们 app 范围内的存储 在我们的 app 代码里… 有更好的地方可以放置它
我们回到 app 代码仔细看看 看看我们如何将它链接到模型
这个是我们开始的那个代码 请注意 它和我们刚才看的 视图代码很相似 我们有符合 app 协议的结构…
而且它有主体属性 就像视图一样 我们在这里构建我们想要的东西
在这个案例中 我们有 WindowGroup 这样我们可以为 app 里的所有窗口指定 我们想要使用的视图 我们 app 的特别之处是 我们也有这个 @main 属性
这会告诉 Swift 这个结构应该是我们 app 的起点
我会在这儿添加我们的商店 和一个 StateObject
app 可以使用 State StateObject 和其他特殊属性 就像视图一样 接下来 我们把商店传递给视图代码 我们将它传递给视图的初始化程序
回到我们的视图代码…
我们用商店的一个属性… 替换恒定的三明治
然后我们会告诉 SwiftUI 我们想要通过把这个对象 变成 ObservedObject 来观察它的变化
我们会更新列表 以便从商店调取三明治
最后 我们还要更新预览 来使用我们的测试商店
很好 现在我们正从商店调取数据 也就是说 我们已经准备好 添加编辑支持了
我要插入一些便捷函数 来从片段中对我们的商店进行更改
一个用来添加新三明治…
一个用来移动三明治 一个用来删除三明治
在 ForEach 列表中 我们可添加一 onMove 修改器…
这样调用了我们的“moveSandwiches”方法
我们还要添加 onDelete…
来调用“deleteSandwiches”
有了这个改变 我们可以回到 app…
我们已经能够从列表中滑动删除数据行 每当我们滑动删除时
SwiftUI 都会调用回调函数
这样会从商店删除该三明治
我们的用户界面会自动更新以显示更改 在 macOS 上 这就是编辑支持所需的东西 但在 iOS 中 除了滑动删除外 我们应该添加一种 明确进入编辑模式的方式 我们添加一个编辑按钮作为工具栏项
我可以使用一个工具栏 修改器…
这样使我们可以添加任何 SwiftUI 视图作为工具栏项 在这里面 我会添加一个编辑按钮 这是一个自动切换编辑模式的控件
我只希望这个在 iOS 上出现 所以 围绕这个按钮 我添加“if os(iOS)”…
这样它只被添加到工具栏这里
接下来 我们切换列表的编辑模式
请注意 我们所有的数据行都有编辑控件 底部的静态元素没有编辑控件 SwiftUI 只会在需要的数据行 显示编辑控件 不需要的数据行则会省略掉
我们可以将列表重新排序… 并点击删除它们
我们还要为能够添加新三明治 添加一个按钮 我会在工具栏修改器中添加另一个视图 针对这个 我会把它做成 带有“添加”标签的按钮…
以及可以调用 makeSandwich 方法的操作
现在我们可以点击按钮…
新三明治就有了
我们快速回顾一下我们添加的东西 我们看到了 如何使用这些修改器快速添加…
编辑操作到列表…
以及一些改变数据的简单函数
还记得之前我们是如何 让三明治类型可识别的吗?
ForEach 自动监视对其收藏的更改 并为我们整合正确的插入和删除 所以我们不再需要通知列表 添加和删除数据行 也就是说我们不用再担心 得到数据源不一致的异常情况
我们还用了一个工具栏修改器…
为编辑我们的列表并添加新商品 添加工具栏项
这就是我们的列表 我们只用了很少的视图代码 就做成了这个复杂的列表用户界面
我们能够很快创建好这个 app 但你也许会想 要把这个 app 交到客户手上使用 我们还有很多工作要做 现如今 人们期望 app 支持动态类型 深色模式、本地化及更多 但借助 SwiftUI 针对这些行为 你可以自动获得更多支持 我们可以使用预览 很快地测试所有这些行为
我们去预览看一下
我要通过点击“预览加”按钮 添加第二预览
然后我可以点击这个“查看”按钮 配置新的预览
我把动态类型的大小设为更大的值
一切会自动看起来都很好
我们来看看那个被添加来 改变这个预览的代码
Xcode 刚在预览环境中添加了 设置值的修改器 环境是一种设置视图上下文信息的方法 这些信息会沿视图层次结构向下流动 并会立即更改任何涉及到视图的方面
它非常适合对视图 及其子视图进行级联更改
我们来添加另一个预览实例
这次 在我们的预览 inspector 面板中…
我们把配色方案设为“黑色”
一切仍会自动看起来很美观 最后 我们来看看我们的 app 与其他语言的匹配情况 我有一些英文字符串文件… 我会放入我们的 app
然后我会告诉 Xcode 我们想要本地化这些文件
然后我会找到我的项目文件…
将本地化导入阿拉伯语
现在回到我们的视图代码 我们再添加一个预览
我们把环境的 layoutDirection…
设为从右到左…
一切运行正常 这样很棒
最后 我们把地区语言…
设为阿拉伯语…
我们的 app 就被本地化了 但更妙的是 如果你回顾我们的代码…
会发现我们没做什么特别的事 来支持这些功能 为了让我们的文本可本地化 我们不必标记哪些字符串 是否应该可本地化 SwiftUI 自动用字符串字面量推断文本 比如“三明治” 默认情况下应该本地化
但从字符串创建的文本 比如我们的模型值 应按原样使用
你甚至可以使用字符串插值 并使它们正确本地化 我们真的很兴奋 你会开始用 SwiftUI 创建 app 等你自动获得所有这些行为后 你就可以专注于你的 app 的独特部分 并更快地为你的使用者创建更好的 app
我们最后再看一下我们的 app 回顾我们创建了什么 并确保一切运行正常 我们使用深色模式版本 重新启动我们的 app 但这次我们在某一设备上来操作
我插入了一部 iPhone 我们点击这个按钮…
把我们的视图发送到设备 以在设备上预览
我们有三明治的列表 我们可以点击一个查看更多信息
在我们的详细视图中 我们可以点击放大到全屏 这一过程在过渡中隐藏了“辣”主题 该动画总是可交互的
我们可以编辑列表以进行改变
我们把这个上移
我是个纯粹主义者 我认为热狗不算三明治
我们可以添加新三明治
这就是我们的 app 但我想要指出最后一点 是我们没有看到的方面 我们在没有构建和运行 app 的情况下 打造了整个 app 并测试了所有这些丰富行为 Xcode 预览使我们能够比以往更快地 查看、编辑以及调试 app 感谢观看 希望大家像我们一样 喜欢使用 SwiftUI
-
-
17:18 - Views are lightweight
struct SandwichDetail: View { let sandwich: Sandwich var body: some View { Image(sandwich.imageName) .resizable() .aspectRatio(contentMode: .fit) } }
-
18:30 - Views are composed
struct SandwichDetail: View { let sandwich: Sandwich var body: some View { Image(sandwich.imageName) .resizable() .aspectRatio(contentMode: .fit) } }
-
19:52 - View are dynamic
struct SandwichDetail: View { let sandwich: Sandwich @State private var zoomed = false var body: some View { Image(sandwich.imageName) .resizable() .aspectRatio(contentMode: zoomed ? .fill : .fit) .onTapGesture { zoomed.toggle() } } }
-
21:40 - Where is truth?
struct SandwichDetail: View { let sandwich: Sandwich @State private var zoomed = false var body: some View { Image(sandwich.imageName) .resizable() .aspectRatio(contentMode: zoomed ? .fill : .fit) .onTapGesture { zoomed.toggle() } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。