SwiftUI学习100天(Day82 - 项目 16,第四部分)

原创链接:https://www.hackingwithswift.com/100/swiftui
以下内容仅供学习参考:

是时候开始将你的新技术付诸实践了,这个项目太大了,需要三天的实施时间才能完成。但今天是第 82 天,所以你已经证明了你有创造奇迹的意志力——正如航空先驱 Amelia Earhart 曾经说过的,“最困难的事情是行动的决定,剩下的只是坚韧。”
今天介绍了很多有趣的技术,但我也会简要地向你介绍filter()
序列方法。我们之前看过map()
,它用于将序列中的对象从一个事物转换为另一个事物,filter 的工作方式类似:它是一个序列方法,它接受一个闭包,该闭包分别在每个元素上运行,并返回一个新阵列。
不同之处在于我们传递的闭包filter()
用作谓词——用于确定每个元素是否应包含在返回数组中的测试。如果测试为某个元素返回 true,则它会被包含,否则将被跳过。
map()
和
范畴。这在我的书Pro Swift中有详细介绍,但简略的定义是我们的代码告诉计算机做什么而不是如何做。在filter()
都属于称为函数式编程的map()
中我们说“遍历这个数组中的每个项目,使用这个闭包转换它,并将结果放回一个新数组”
但是要由 Swift 来弄清楚如何实现这一点。filter()的情况下,我们在做很多相同的事情:“遍历这个数组中的每一项,对每一项运行这个测试,并将通过测试的任何项放入一个新数组中。”
不管怎样,聊够了——你今天有很多事情要完成,所以让我们开始写代码吧!
今天你要完成三个主题,你将在其中了解选项卡视图、环境对象filter()
等。

构建我们的标签栏
这个应用程序将在标签栏中显示四个 SwiftUI 视图:一个显示你遇到的每个人,一个显示你联系过的人,另一个显示你没有联系过的人,最后一个显示你的个人信息给其他人扫描。
前三个视图是同一概念的变体,但最后一个则完全不同。因此,我们可以只用三个视图来表示我们所有的 UI:一个显示人员,一个显示我们的数据,一个使用TabView
.
因此,我们的第一步是为我们的选项卡创建占位符视图,我们可以稍后返回并填写。按 Cmd+N 创建一个新的 SwiftUI 视图并将其命名为“ProspectsView”,然后创建另一个名为“MeView”的 SwiftUI 视图。你可以将它们都保留为默认的“Hello, World!” 文本视图;现在没关系。
现在,重要的是ContentView
,因为这是我们要存储我们TabView
的 UI 中包含所有其他视图的地方。我们很快会在这里添加更多逻辑,但现在这只是一个TabView
具有三个实例ProspectsView
和一个MeView
. 这些视图中的每一个都会有一个tabItem()
修饰符,其中包含我从 SF Symbols 中挑选的图像和一些文本。
用这个替换你当前的主体ContentView
:
如果你现在运行该应用程序,你会在屏幕底部看到一个整洁的标签栏,允许我们点击我们的四个视图中的每一个。
现在,显然创建三个ProspectsView
的实例
在实践中会很奇怪,因为它们完全相同,但我们可以通过自定义每个视图来解决这个问题。请记住,我们希望第一个显示你遇到的每个人,第二个显示你联系过的人,第三个显示你没有联系过的人,我们可以用一个枚举加上一个属性来表示ProspectsView
。
所以,现在在ProspectsView
里面添加这个枚举
:
现在我们可以通过给它一个新属性来使用它来允许每个ProspectsView
实例
略有不同:
这将立即中断ContentView
,ProspectsView_Previews
因为他们需要在创建时为该属性提供一个值ProspectsView
,但首先让我们通过给它们一个导航栏标题来使用它来自定义三个视图中的每一个。
首先将此计算属性添加到ProspectsView
:
现在替换默认的“Hello, World!” 正文:
这至少让每个ProspectsView
实例看起来都略有不同,因此我们可以确保选项卡正常工作。
为了使我们的代码再次编译,我们需要确保每个ProspectsView
初始化程序都使用过滤器调用。因此,将ProspectsView_Previews
正文更改为:
然后更改三个ProspectsView
实例,ContentView
使它们分别具有filter: .none
、filter: .contacted
和filter: .uncontacted
。
如果你现在运行该应用程序,你会发现它看起来好多了。现在面临真正的挑战:前三个视图需要处理相同的数据,那么我们如何才能顺利共享它们呢?为此,我们需要求助于 SwiftUI 的环境……



使用@EnvironmentObject 跨选项卡共享数据
SwiftUI 的环境让我们以一种非常漂亮的方式共享数据:任何视图都可以将对象发送到环境中,然后任何子视图都可以在以后从环境中读回这些对象。更好的是,如果一个视图更改了对象,所有其他视图都会自动更新——这是在大型应用程序中共享数据的一种非常聪明的方式。
在我们的应用程序中,我们有一个TabView
包含三个实例的ProspectsView
,我们希望所有这三个实例都作为同一共享数据的不同视图工作。这是 SwiftUI 环境有意义的一个很好的例子:我们可以定义一个存储一个潜在客户的类,然后将这些潜在客户的数组放入环境中,这样我们所有的视图都可以在需要时读取它。
因此,首先创建一个名为 Prospect.swift 的新 Swift 文件,将其 Foundation 导入替换为 SwiftUI,然后为其提供以下代码:
是的,那是一个类而不是结构。这是有意为之的,因为它允许我们直接更改类的实例,并同时在所有其他视图中更新它。请记住,SwiftUI 负责自动将更改传播到我们的视图,因此不存在视图过时的风险。
当谈到在多个视图之间共享时,SwiftUI 环境的最好的事情之一是它使用ObservableObject
我们一直使用的与@StateObject
属性包装器相同的协议。这意味着我们可以标记应该使用@Published
属性包装器声明的属性——SwiftUI 会为我们处理大部分工作。
所以,在 Prospect.swift 中添加这个类:
稍后我们会回过头来,尤其是让初始化器做的不仅仅是创建一个空数组,但现在已经足够了。
现在,我们希望所有ProspectsView
实例共享该类的一个Prospects
实例
,因此它们都指向相同的数据。如果我们在这里编写 UIKit 代码,我会详细解释要做到这一点有多困难,以及我们需要多小心以确保所有更改都能干净地传播,但对于 SwiftUI,它只需要三个步骤。
首先,我们需要添加一个属性给ContentView
来
创建和存储类的单个实例Prospects
:
其次,我们需要将该属性发布到 SwiftUI 环境中,以便所有子视图都可以访问它。因为选项卡被认为是它们所在的选项卡视图的子项,所以如果我们将它添加到TabView
环境中,那么
我们所有的ProspectsView
实例都将获得该对象。
因此,将此修饰符添加到ContentView
的
TabView
:
重要提示:确保将相同的修饰符添加到ProspectsView
的预览结构中
,以便你的预览继续工作。
现在我们希望 ProspectsView
的所有实例
在创建时从环境中读回该对象。这使用了一个新的@EnvironmentObject
属性包装器来完成查找对象、将其附加到属性以及随着时间的推移使其保持最新的所有工作。所以,最后一步只是将此属性添加到ProspectsView
:
这真的就是它所需要的——我认为 SwiftUI 没有办法让这一切变得更容易。
重要提示:当你使用@EnvironmentObject
时,
你是在明确告诉 SwiftUI,你的对象将在创建视图时存在于环境中。如果它不存在,你的应用程序将立即崩溃 - 请小心,并将其视为隐式解包的可选项。
很快我们将添加代码以通过扫描 QR 码添加潜在客户,但现在我们将添加一个导航栏项目,该项目仅添加测试数据并将其显示在屏幕上。
将ProspectsView
为此:的body
属性更改
现在你会在选项卡视图的前三个视图上看到一个“扫描”按钮,点击它会同时向所有三个视图添加一个人——无论你点击哪个按钮,你都会看到计数增加。



动态过滤 SwiftUI 列表
SwiftUI 的List
视图喜欢使用符合Identifiable
协议的对象数组,或者至少可以提供某种id
保证唯一的参数。然而,没有理由需要将这些存储在视图的属性中,事实上,如果我们发送一个计算属性,那么我们就可以根据需要调整我们的过滤。
在我们的应用程序中,我们有三个实例,ProspectsView
仅根据FilterType
从选项卡视图传入的属性而有所不同。我们已经使用它来设置每个视图的标题,但我们也可以使用它来设置List
.
最简单的方法是使用 Swift 的filter()
方法。这将通过你作为闭包提供的测试运行序列中的每个元素,并且从测试中返回 true 的任何元素都将作为新数组的一部分发回。我们的ProspectsView
已经有一个prospects
正在传递的属性,其中包含一系列人员,因此我们可以返回所有人员、所有联系过的人或所有未联系过的人。
将此属性添加到ProspectsView
前两个下面:
当filter()运行时
,people
数组中的每个元素都
会
通过我们的测试。所以,$0.isContacted
意思是“当前元素的isContacted
属性是否设置为真?” 数组中通过该测试的所有项目(isContacted
已
设置为 true)都将被添加到新数组并从filteredResults
返回
. 而当我们使用!$0.isContacted
时,
我们会得到相反的结果:只包括尚未联系的潜在客户。
有了这个计算属性,我们现在可以创建一个List
遍历该数组的循环。这将使用 VStack
显示每个潜在客户的标题和电子邮件地址
,我们还将使用ForEach
以便稍后添加删除。
用这个替换现有的文本视图ProspectsView
:
如果你再次运行该应用程序,你会发现情况开始好转了。
在我们继续之前,我希望你考虑一下:既然我们正在使用计算属性,SwiftUI 如何知道在属性更改时刷新视图?答案其实很简单:不是。
当我们向 @EnvironmentObject
中添加一个
属性时,我们还要求 SwiftUI在该属性发生更改时重新调用该ProspectsView
的
body
属性。
因此,每当我们将一个新人插入到数组中时,people
的@Published
属性包装器将向所有正在观察它的视图宣布更新,并且 SwiftUI 将重新调用ProspectsView
的
. 这反过来又会再次计算我们的计算属性,因此body
List
会发生变化。
我喜欢 SwiftUI 在这里透明地为我们承担这么多工作的方式,这意味着我们可以专注于我们如何过滤和呈现我们的数据,而不是如何连接所有管道以确保事情保持最新。


