大多数浏览器和
Developer App 均支持流媒体播放。
-
揭开 SwiftUI 的神秘面纱
深入了解 SwiftUI 理念的核心原则:身份、生命周期和相关性。了解常见模式,了解驱动框架的原则,并了解如何使用它们来保证 app 的正确性和性能。
资源
相关视频
WWDC23
WWDC22
WWDC21
WWDC20
WWDC19
-
下载
♪低音音乐播放♪ ♪ 马特里克特森:大家好 我是马特 稍后路卡和拉吉也会加入我们 今天 我们将揭开 SwiftUI的神秘面纱 现在 我们已经多次听说SwiftUI 是一个声明式UI框架 这意味着你要在高层次上 描述你想要的app 而由SwiftUI决定如何实现它 现在 在大多数时候 这很好用! 这就是你感觉SwiftUI非常神奇的时候 但是总会有这样的时刻 SwiftUI会做一些 你可能意想不到的事情 在那些时刻 它有助于更多地了解 SwiftUI在幕后所做的事情 建立更好的直觉 以了解如何获得 你正在寻找的结果 所以今天的问题是 当SwiftUI查看你的代码时 它看到了什么? 答案是三件事 身份、生命周期和依赖关系 身份是SwiftUI在app的 多次更新中 识别相同或不同元素的方式 生命周期是SwiftUI随时追踪视图 和数据存在的方式 依赖关系是SwiftUI理解你的界面 何时需要更新以及为什么需要更新 这三个概念共同说明了SwiftUI 如何决定需要更改的 内容、方式和时间 从而做出你在屏幕上看到的 动态用户界面 今天 我们将更深入地 逐一讨论这些概念 让我们从身份开始 这里有几个朋友可以帮助我 这些红宝石猎犬很可爱 但它们也是一个深刻哲学问题的主题 这是两只不同的狗吗? 或者这些实际上是 同一只狗的两张照片? 事实是 这根本不可能识别出来! 我们并没有足够的信息 而事物相同与否的问题 就是我们所谓的“身份”的核心 身份不仅对狗很重要 这也是SwiftUI 如何理解你的app的关键 让我们看一个例子 这是我制作的一个名为 “好狗 坏狗”的app 这有助于我追踪 我毛茸茸的朋友是否表现得最好 这很简单 我只需点击屏幕上的任意位置 即可在好坏状态之间切换 那么身份与我的app 有什么关系呢? 嗯 它实际上类似于 我们刚才问的关于狗的哲学问题 看着这些图标 它们是否看起来 像是两个完全不同的视图? 或者它们可能是相同的视图 只是在不同的地方和不同的颜色? 这种区别实际上很重要 因为它改变了我们的界面 从一种状态过渡到另一种状态 假设这些图标实际上是不同的视图 这意味着图标应该独立过渡 例如淡入淡出 但是 如果它们实际上 是相同的视图呢? 那么相反的 这意味着视图 应该在过渡期间在屏幕上滑动 因为是同一个视图 从一个位置移动到另一个位置 所以连接不同状态的视图很重要 因为这就是SwiftUI理解 如何在它们之间过渡的方式 这是视图身份背后的关键概念 共享相同身份的视图代表 同一概念下UI元素的不同状态 相比之下 代表不同UI元素的视图 将永远具有不同的身份 在座谈的后面 路卡和拉吉将讨论 视图身份对应用程序的数据 和更新周期的实际影响 现在 让我们看看身份 是如何在代码中表示 重点在于 SwiftUI使用的两种不同类型的身份 一 显式身份 使用自定义或数据驱动的标识符 第二 结构身份:区分视图 根据它们在视图 层次结构中的类型和位置 现在 为了帮助理解这些概念 让我向你介绍更多我的朋友 好的 请记住 区分狗可能很困难 尤其是当它们看起来相同时 那么什么样的额外信息 可以帮助我们识别我们的狗呢? 一种方法是简单地询问它们的名字 如果两只狗长得一样 名字也一样 那么我会说事实上 这很有可能它们是同一条狗 但如果他们有不同的名字 那么我们可以保证 其实它们是不同的狗 像这样分配名称或标识符 是显式身份的一种形式 显式身份是强大而灵活的 但确实需要某个地方的某个人 追踪所有这些名称 你可能已经习惯的这种 显式身份形式 是个指针身份 它在整个UIKit和AppKit中被使用着 而现在 SwiftUI不使用指针身份 但了解它会帮助你更好地理解 SwiftUI的工作方式和不同之处 让我们快速浏览一下 想想一个UIKit或AppKit 视图层次结构 就像这样 由于UIViews和NSViews是类别 它们每个都有一个指向 它们的内存分配的唯一指针 这个指针是显式身份的自然来源 我们可以只使用它们的指针 来引用单个视图 如果两个视图共享同一个指针 我们可以保证它们确实是相同的视图 但是SwiftUI不使用指针 因为SwiftUI视图是值类型 通常表示为结构而不是类 在2019年的《SwiftUI要点》讲座中 我们讨论了为什么 SwiftUI使用值类型 而不是其视图的类 所以我建议观看那个 座谈以了解更多信息 现在 重要的是要知道 值类型没有规范引用 SwiftUI可以用作其视图的持久身份 相反地 SwiftUI依赖于 其他形式的显式身份 例如 看看这个救援犬列表 此处使用的id:参数 是一种显式身份形式 每个救援犬的dogTagID被用于 在列表中明确身份其对应的视图 如果救援犬的集合发生变化 SwiftUI可以使用这些ID 来了解究竟发生了什么变化 并在列表中生成正确的动画 在这种情况下 SwiftUI甚至能够正确地 为在不同部分之间 移动的视图设置动画 让我们看一个更高级的例子 在这里 我们使用ScrollViewReader 使用底部的按钮跳转到视图的顶部 id修饰符提供了一种 使用自定义标识符 明确身份视图的方法… 在这种情况下 是我们在 页面顶部的标题视图 然后我们可以将该标识符 传递给滚动视图代理的scrollTo方法 告诉SwiftUI转到该特定视图 这样做的好处是 我们不必明确识别每个视图 只是我们需要在代码中的 其他地方引用的那些 比如我们的标题文本 相比之下 ScrollViewReader ScrollView、backstoryText 和Button不需要显式标识符 但仅仅因为他们的身份不明确 并不意味着这些视图根本没有身份 因为每个视图都有一个身份 即使它不是显式的 这就是结构身份派上用场的时候了 SwiftUI使用视图层次结构 为视图生成隐式身份 所以你就不必用了 现在 让我请更多我的朋友 来协助我解释我的意思 假设我们有两只相似的狗 但我们不知道他们的名字 但我们仍然需要识别每一个 好吧 假设这些是非常好的狗 并且它们能够非常安静地坐着 如果我们能保证他们不动 我们就可以根据 他们坐的位置来识别他们 比如“左边的狗”或“右边的狗” 我们正在使用我们主题的相对排列 把它们区分开来… 这就是结构身份 SwiftUI在整个API中 都利用了结构身份 一个经典的例子是在View代码中 使用if语句和其他条件逻辑的时候 条件语句的结构为我们提供了 一种清晰的方式来识别每个视图 第一个视图仅在条件为真时显示 而第二个视图仅在条件为假时显示 这意味着我们总是可以分辨 哪个视图是哪个 即使它们看起来很相似 但是 这只有在 SwiftUI可以静态地保证 这些视图保持原样 并且永不交换位置时才有效 SwiftUI通过查看视图层次 结构的类型结构 来实现这一点 当SwiftUI查看你的视图时 它看到了它们的通用类型… 在这种情况下 我们的if语句会转换为 _ConditionalContent视图 这是通用的真假内容 这种转译是由ViewBuilder所提供 它是Swift中的一种结果构建器 View协议暗中将它的 body属性包装在一个 ViewBuilder中 它构建了 一个单一的通用视图 来自我们属性中的逻辑语句 我们的body属性的View返回类型 是一个占位符 代表这个静态复合类型 将其隐藏起来 以免弄乱我们的代码 使用这种泛型类型 SwiftUI可以保证 真正的视图永远是 AdoptionDirectory 而错误的视图将始终是DogList 允许在幕后为它们分配一个隐式的 稳定的身份 事实上 这是理解之前的 “好狗 坏狗”app的关键 使用顶部的代码 我们有一个if语句 为每个条件分支定义不同的视图 这将导致视图转换进出 因为SwiftUI能理解 if语句的每个分支 代表着具有不同身份的不同视图 或者 我们可以只有一个PawView 来改变它的布局和颜色 当它转换到不同的状态时 视图将平滑地滑动到下一个位置 那是因为我们正在通过 一致的身份来修改单个视图 这两种策略都可以 但是SwiftUI通常推荐第二种方法 默认情况下 尽量保留身份 并提供更流畅的过渡 这也有助于保持视图的 生命周期和状态 而路卡将会在稍后详细讨论这个 现在我们了解了结构身份 我们需要谈谈 它的邪恶宿敌 AnyView 要了解使用AnyView的影响 让我们看一下它在你视图的结构上 所具有的效果 之前我们写了这个if语句 来在AdoptionDirectory和DogList 之间做切换 当SwiftUI查看此代码时 它会在右侧看到泛型类型结构 现在让我们看一个不同的例子 一个广泛使用AnyView的例子 这是我编写的一个辅助函数 用于获取代表狗品种的视图 函数中的每个条件分支 都返回不同类型的视图 所以我把它们都包装在AnyViews中 因为对于整个函数 Swift需要一个 单一的返回类型 不幸的是 这也意味着 SwiftUI看不到 我的代码的条件结构 相反 它只看到一个AnyView 作为函数的返回类型 这是因为AnyView就是所谓的 一个“类型擦除包装类型” 它从其通用签名中隐藏了 它所包装的视图的类型 但也许更重要的是 对我们人类而言 这段代码也很难阅读 让我们看看我们是否可以 简化这段代码 并使SwiftUI可以看到更多的结构 首先 看起来这个分支在我们的 BorderCollieView 旁边有条件地添加了一个SheepView 如果附近有羊的话 我们可以通过有条件地 在HStack中添加视图 而不是有条件地在我们的 视图周围添加HStack 有了这个变化 现在很容易看到 我们只是从每个分支返回一个视图 所以我们的本地dogView 变量不是必需的 相反地 我们可以用每个分支内的 return语句替换它 正如我们之前所见 普通的SwiftUI视图代码 可以使用返回不同类型视图的if语句 但是如果我们只是 尝试从我们的代码中删除 return语句和AnyViews 我们会看到一些错误和警告出现 这是因为SwiftUI 需要来自我们辅助函数的 单一返回类型 那么我们怎样才能避免这些错误呢? 回想一下 视图的body属性是特殊的 因为View协议在ViewBuilder中 暗中包装了它 这会将属性中的逻辑转换为 单个的、通用的视图结构 现在 Swift默认不会将辅助函数 推断为视图构建器 但我们可以选择加入 通过自己手动应用ViewBuilder属性 这使我们能够在没有任何警告 或错误的情况下 删除return语句和AnyView包装器 好的 我们的代码现在看起来不错! 我们已经去掉了所有的AnyView 使它比以前更容易阅读 如果我们查看结果的类型签名 它现在完全复制了条件逻辑 我们的函数带有条件内容树 为SwiftUI提供了更丰富的视角 及其组件的身份 但是我们还可以做一个小的改进 我们函数的顶层只是匹配 狗品种的不同情况 这似乎是switch语句的 一个很好的用例 视图构建器也支持它 现在更容易快速理解 我们视图的所有不同情况 因为switch语句实际上只是 条件语句的语法糖 我们在右侧生成的视图的类型签名 保持完全相同 退一步看看 我们刚刚向你展示了 AnyViews如何从你的代码中 删除类型信息 并介绍如何通过利用视图构建器 来摆脱不必要的AnyView 通常 我们建议尽可能 避免使用AnyView AnyView太多通常会使代码 更难阅读和理解 传统的控制流语句 如if/else和switch能使其更容易 查看视图的不同可能状态 因为AnyView对编译器隐藏了 静态的类型信息 它有时可以防止有用的 诊断错误和警告 出现在你的代码中 最后 请记住 在不需要时 使用AnyView会导致性能下降 可以的话 请使用泛型 来保留静态类型信息 而不是在代码中传递AnyView 至此 我们已经完成了SwiftUI中 视图身份的基本类型的介绍 通过明确的身份 我们可以将我们的视图的身份 与我们的数据联系起来 或提供自定义标识符以引用特定视图 通过结构身份 我们已经了解了SwiftUI 如何识别我们的视图 仅依靠它们在视图层次结构中的 类型和位置 现在我将把事情交给路卡 讨论如何认同你的视图 与他们的寿命和状态有关 路卡贝尔纳迪:谢谢 马特 既然我们了解了SwiftUI 如何识别你的视图 让我们探索如何识别 这关系到你的视图 和数据的生命周期 这将帮助你更好地了解 SwiftUI的工作原理 为了帮助说明这一点 我也打算带一个朋友过来 这是忒修斯 他是不是也很可爱? 有人会说更可爱 但我离题了 我们非常直观地认为 一旦我们为我们最喜欢的宠物命名 他将永远是同一只可爱的猫 即使他处于不同的状态 他一整天都在移动 当我们看他的那瞬间 他可能困了 过了一会儿 身为一只正常的猫 他觉得我很烦 但他永远是忒修斯 这是将身份与生命联系起来的本质 身份允许我们随着时间的推移 为不同的值定义一个稳定的元素 换句话说 它允许我们 随着时间的推移引入连续性 你可能想知道 这要如何适用于SwiftUI? 所以让我们把话题 从马特开发的app 回到一个对猫友好的版本来 就像忒修斯可以在不同的时刻 处于不同的状态一样 我们的视图也是 在其生命周期处于不同的状态 对于我们的视图来说 每个状态都有不同的值 身份连接着这些不同的值 随着时间的推移 作为一个单一的实体 一个视图 让我们看些代码来厘清这一点 在这里 我们有一个简单的视图 显示了咕噜声的强度 剧透:忒修斯的声音很大 通过对主体的评价 SwiftUI将为这个视图创建一个新值 在这种情况下 强度值为25 忒修斯越来越饿 需要更多的关注 主体再次以更高的强度被调用 并创建视图的新值 这是从相同的视图定义 创建出的两个不同的值 SwiftUI将保留值的副本 执行比较 并了解视图是否已更改 但在那之后 值被破坏了 重要的是要理解 视图值与视图身份不同 视图值是短暂的 你不应依赖其生命周期 但你能控制的是其身份 当一个视图第一次被创建并出现时 SwiftUI会使用之前讨论的 技术组合为其分配身份 随着时间的推移 在更新的驱动下 视图的新值将会被创建出来 但从SwiftUI的角度来看 这些代表了相同的视图 一旦视图的身份发生变化 或者视图被删除 它的生命周期就会结束 每当我们谈论视图的生命周期时 我们指的是身份的持续时间 与该视图相关联 能够将视图的身份 与其生命周期联系起来 是理解SwiftUI 如何持久化你的状态的基础 因此 让我们将State 和StateObject带入画面 当SwiftUI正在查看你的视图并看到 State或StateObject时 它知道它需要在视图的 整个生命周期中保留该数据 换句话说 State和StateObject 是持久化存储 并与你的视图身份相关联 在视图身份的开始 当它第一次被创建时 SwiftUI将使用它们的初始值 来为State和StateObject 分配内存中的存储 在这里 我们专注于标题的状态 在视图的整个生命周期中 SwiftUI将会在此存储 发生变异时保留它 并且重新评估视图的主体 让我们看一个具体的例子 说明身份的变化 如何影响状态的持久性 这是个有趣的例子 因为我们有相同的视图 但在两个不同的分支中 如果你之前还记得 因为结构相同 这两种视图被认为具有不同的身份 马特讨论了这如何影响动画 但在你的状态的持久性 也有深远的影响 让我们在实践中看看 当我们第一次评估主体 并进入真正的分支时 SwiftUI将为其初始值 分配持久存储 在这个视图的整个生命周期中 SwiftUI会在状态因各种操作 而发生变化时维持状态 但是如果dayTime的值发生变化 并且我们输入false分支 会发生什么? SwiftUI知道这是一个 具有独特身份的 不同视图 它为false视图创建新的存储 从状态的初始值开始 并且在之后立即释放真实视图的存储 但是如果我们回到真正的分支呢? 嗯 这又是一个新视图 所以SwiftUI会创建新的存储 再次从状态的初始值开始 这里的要点是 每当身份发生变化时 状态就会被替换 让我在这里暂停一下 确保你 理解这一要点 状态的持久性 与视图的生命周期有关 这是一个非常强大的概念 因为我们可以清楚地分开 视图的本质是什么…它的状态… 并将其与其身份联系起来 其他一切都可以从中衍生出来 你的数据是如此重要 以至于SwiftUI有一套 使用数据身份的数据驱动结构 来作为你视图的一种明确身份 这方面的典型示例是ForEach 现在让我们来看看初始化ForEach的 所有不同方式 这将帮助我们在这种类型中 培养更好的直觉 ForEach的最简单形式 是采用恒定范围的形式 这是一个非常方便的初始化程序 尤其是当你开始使用制作 一些新UI的原型时 SwiftUI将使用此范围内的偏移量 来识别视图构建器生成的视图 通过要求一个恒定的范围 我们保证在视图的生命周期内 身份是稳定的 事实上 在动态范围内 使用这个初始化器是错误的 在今年新增的是 你会在提供非常量范围时 看到一个警告 让我们来把事情变得更有趣 并引入动态的数据集合 这个初始化器接受一个集合 和一个作为标识符的属性的关键路径 此属性必须是可散列的 因为SwiftUI 将使用其值来对从集合的元素中 生成的所有视图分配身份 稍后 拉吉将向你展示一些示例 有关选择稳定身份如何影响 你的应用程序的性能和正确性 这种为数据提供稳定身份的想法 非常重要 以至于标准库 定义了可识别协议 来描述此功能 并且SwiftUI充分利用了这个协议 让你可以省略关键路径 并使用协议要求提供的标识符 来定义与你的数据相关联的身份 和你的视图 我真正喜欢Swift的一点是 我们可以利用它的类型系统 精确描述我们正在解决的 问题的约束 所以请让我看看我们在这里 所使用的 初始化程序的定义 在这个简短的定义中 有很多有趣的东西 所以让我们尝试解开它们 ForEach需要 两个主要部分:一个集合 这里由通用参数Data表示 以及从集合的每个元素 生成视图的方法 这个初始值设定项的形状应该给了你 ForEach在数据集合和视图集合之间 定义关系的直觉 但实际上 这里最有趣的部分 是我们约束集合的元素 是Identifiable 同样 Identifiable协议的目的是 允许你的类型 提供稳定的身份概念 以便SwiftUI在其整个生命周期中 可以追踪你的数据 其实这和我们之前讨论的 身份和生命周期的概念非常相似 采用可识别类型的SwiftUI视图 和视图构建器是数据驱动的组件 这些视图使用你提供的数据身份 来确定生命周期 与其相关的视图 选择一个好的标识符是你 控制视图和数据生命周期的机会 因此 让我们回顾一下 我们在本节中讨论的内容 视图值是短暂的 你不应该依赖它们的生命周期 但他们的身份不是 而是让他们随着时间的推移 具有连续性 你可以控制你的视图的身份 并且你可以使用身份 明确界定状态的生命周期 最后 SwiftUI充分利用了 数据驱动组件的可识别协议 所以为你的数据 选择一个稳定的标识符很重要 现在延续传统 我要把它交给拉吉 拉吉? 拉吉拉玛莫锡:谢谢 路卡! 到目前为止 我们已经解释了 身份是什么 以及它如何与视图的生命周期相关联 接下来 我将深入探讨 SwiftUI如何更新UI 目标是为你提供一个更好的 思维模型来构建SwiftUI代码 我还将展示一些示例 在最后概述所有内容 为了结束对依赖关系的讨论 让我们来看一个视图 这是一个简单的视图 它显示了一个按钮 用零食来奖励一只狗 对不起 路卡 但我更像一个爱狗的人 让我们专注于视图的结构 首先 让我们看看顶部 有两个属性 一个用于狗 另一个用于零食 这些属性是视图的依赖项 依赖项只是视图的输入 当依赖项发生变化时 视图需要生成一个新的主体 主体是你为视图构建层次结构的地方 深入这个视图的层次结构 我们有一个带有动作的按钮 操作是触发视图依赖项更改的原因 让我们将代码换成等效的图表 这是我们的DogView的图表 当我们点击按钮时 它会发送一个动作来奖励狗 我们的狗一口吞下食物 这导致了狗的变化 也许他想要再来一个 因为依赖改变了 DogView 产生了一个新的主体 想了解有关SwiftUI中的数据流 基本概念的更多信息 请查看 WWDC 2020的 《SwiftUI中的数据要点》 接下来 让我们稍微简化一下这张图 专注于视图层次结构 注意我们的视图 是如何形成树状结构的 如果我们添加狗 并在顶部处理依赖项 它仍然看起来像一棵树 然而 DogView并不是 唯一具有依赖关系的视图 在SwiftUI中 每个视图都可以 有自己的一组依赖项 到目前为止 这仍然看起来像一棵树 但请注意 可能有多个视图 依赖于相同的状态或其他数据 例如 其中一个后代 也可能依赖于狗 这可能发生在我们的其他依赖项之一 所以我们从一棵树开始 但是这个结构 现在只是松散地类似于一棵树 事实上 如果我们重新排列它 以避免重叠线条 我们最终会得到这种结构 这摆明是一个图形 而不是一棵树 事实上 我们称 这种结构为“依赖图” 这个结构很重要 因为它允许SwiftUI 有效地只更新那些 需要一个新主体的视图 以底部的依赖项为例 如果我们检查这个依赖项 它有两个依赖视图 该图的秘密在于 如果依赖关系发生变化 只有那些视图才会失效 SwiftUI将会调用每个视图的主体 为每个视图生成一个新的body值 SwiftUI将会把每个无效视图的 主体的值给实例化 这可能会导致 更多的依赖关系发生变化 但不会总是这样! 因为视图是值类型 SwiftUI可以有效地比较它们 方便仅仅更新正确的视图子集 这是检视路卡 之前讨论的内容的另一种方式 视图的值是短暂的 struct值仅会用于比较 但视图本身俱有更长的生命周期 这就是我们可以避免为中心视图 生成新主体的方法 身份是依赖图的支柱 正如马特所说 每个视图都有身份 无论是明确指定的还是结构化的 该身份是SwiftUI 把改动导到正确视图的方式 并有效地更新用户界面 这边还有很多种依赖关系 我们之前看到了一些带有 零食属性的例子 和狗绑定 但形成依赖关系也可以通过使用 环境、状态 或任何可观察对象的属性包装器 接下来 我想谈谈如何在 你的视图中改进身份的使用 这将有助于SwiftUI 更好理解你的代码 正如路卡所说 视图的生命周期是其身份的持续时间 这意味着标识符的稳定性至关重要 不稳定的标识符 会导致更短的视图生命周期 拥有一个稳定的标识符 也有助于提高性能 因为SwiftUI不需要不断地 为视图创建存储 并通过更新图表进行搅动 正如你之前看到的 SwiftUI使用生命周期 来管理持久化存储 因此稳定的标识符 对于避免状态丢失也很重要 让我们转向一个代码示例 来解释标识符稳定性的重要性 在这个例子中 我有一个 我最喜欢的宠物的列表 我们的pet结构上有一个标识符 但实际上有一个错误 每次我得到一个新宠物时 屏幕上的所有内容都会闪烁! 让我们停下来看看这段代码 你能找出错误在哪里吗? 错误就在这里 在我们的 Identifiable一致性中 如果你没有通过测试 请不要担心 本环节中没有零食 问题是这个标识符不稳定 所以只要数据发生变化 我们就会得到一个新的标识符 如果相反 我们使用我们的 pet数组的索引怎么办? 不幸的是 这也有类似的问题 通过使用索引 视图现在可以 通过它们各自宠物 在集合中的位置来识别 如果我决定我有一个新的 第一个最喜欢的宠物 所有其他宠物 都会改变他们的身份 这可能会导致一个严重的错误 在这个例子中 按钮在索引零处 插入一个新元素 但因为最后一个索引是新的 我们在末尾而不是开头插入 这是因为 与计算出的 随机标识符一样 索引并不是一种稳定的身份形式 在这个例子中 我们需要使用 一个稳定的标识符 比如来自数据库的标识符 或源自宠物的稳定属性 任何持久标识符都是不错的选择 现在我们的动画看起来很棒!
但是稳定性并不是我们需要 好的标识符的唯一属性 良好标识符的另一个属性是唯一性 每个标识符都应映射到单个视图 这确保动画看起来很棒 表现流畅 并且你的层次结构的依赖关系 能以最有效的形式反映 让我们再看一个例子 在这个例子中 我正在处理一个 包含我所有宠物最喜欢的食物的视图 每个零食都有一个名称、图释 和一个到期日期 我选择通过名称来识别每种零食 在这一点上…我相信你能猜到… 我们这里也有一个错误 当我们有不止一种 相同的零食时会发生什么? 我不了解你 但我喜欢批量购买狗饼干 当我将它们添加到罐中时 它们可能不会出现! 问题在于 零食的名称 不是它的唯一标识符 相反 我们可以为每次喂零食 使用序列号或其他唯一的ID 这确保所有正确的数据 都显示在我们的罐中 它还将确保更好的动画 和更好的表现 当SwiftUI需要标识符时 它需要你的帮助! 使用随机标识符时请小心 尤其是在计算属性中 通常 你希望所有标识符都是稳定的 标识符不应随时间变化 新标识符代表 具有新生命周期的新项目 最后 标识符必须是唯一的 多个视图不能共享一个标识符 SwiftUI依赖于这些属性 使你的app运行顺畅且无错误 既然我们已经讨论了明确的身份 我想继续讨论结构身份 在这个例子中 我正在处理之前的零食罐 作为一个负责任的宠物爱好者 我只喂我的宠物最好的 未过期的食物 为了帮助我判断食物何时变质 我添加了一个新的修饰符 可以在零食过期时 选择性地使零食单元变暗 我已突出显示变暗的单元格 让我们深入研究修饰符 你可以在修饰符中看到 我有一个日期 并将其与当前日期进行比较 知道什么时候让视图变暗 乍看这似乎很好 但这里有一个微妙的问题 如果情况发生变化 并且我们的零食过期 我们最终会得到一个新身份 因为这里有一个分支 正如马特所讨论的 分支是一种结构身份 这意味着我们有两个内容副本 而不是一个可选的修改副本 请注意 此处的分支位于修饰符中 为了清楚起见 我已将修饰符 及其使用的站点 放在同一张幻灯片中 但是在你的项目中 你可能在文件中拥有这样的分支 而你甚至都没有意识到这一点! 当然 我们在这里讨论的 所有内容都适用于视图和视图修饰符 那么我们怎样才能避免这种情况呢? 嗯 一种方法是将分支折叠在一起 并在不透明度修饰符内 移动条件 就像这样 通过删除这个分支 我们已经正确地描述了这个视图 作为只有一个身份 此外 移动不透明度修饰符内的条件 可以帮助提升表现 因为我们已经严格限定了 依赖代码的范围 现在当条件改变时 只需要改变不透明度 诀窍是当条件为真时 我们的不透明度为1 看起来像这样 不透明度为1 就是没有效果 我们称这样的修饰符为 “惰性修饰符” 因为它们不会影响渲染结果 SwiftUI修饰符很廉价 因此这种模式几乎没有内在成本 因为没有产生的视觉效果 框架可以有效地修剪掉修饰符 进一步降低其成本 分支很棒 它们存在于SwiftUI中是有原因的 但是当不必要地使用时 它们会导致性能不佳 令人惊讶的动画 并且正如路卡所展示的 甚至是状态丢失 当你介绍一个分支时 先停顿一下 考虑一下你是否代表 多个视图或同一视图的两个状态 正如我们所见 使用惰性修饰符 而不是分支 来辨识单个视图 通常效果更好 这里只是惰性改性剂的几个例子 我特别喜欢 transformEnvironment 用于有条件地写入环境 总结一下 我们今天向你展示了 身份是惊人表现的秘诀之一 我们已经讨论了显式和结构身份 以及如何利用每个身份 改进你的应用程序 从身份 我们可以推导出 一个视图的生命周期 它控制其关联的存储、转换等 并且我们还解释了SwiftUI 使用身份和生命周期来形成依赖关系 并且由可以有效 更新UI的图形来表示 除了揭开SwiftUI的神秘面纱 我们还为你提供了一些提示和技巧 来避免错误 并提高app的性能 现在你已经学会了这些技巧 请浏览一下你的代码 看看它们是否可以帮助你 谢谢收看 并继续打造 出色的app吧! ♪
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。