译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
Hot Prospects:介绍
这个项目我们要来构建 Hot Prospect 应用,这是一个跟踪你在会议中遇到的人的应用。你之前可能见过这样的应用:显示一个二维码,存储你的参与信息,其他人可以扫描这个二维码,以便之后可能的进一步接触。
这个功能听起来很容易,但其实会涉及一些非常重要的技术:创建 tab 栏和上下文菜单,用 environment 分享自定义数据,发送自定义通知,等等。最后的应用很酷,但你在过程中才学到的东西更有用!
向往常一样,我们有在开始真正实现项目之前,要先了解许多技术。请用 Single View App 模板创建一个新的 iOS 项目,并取名 HotProspects。
让我们开始吧!
用 @EnvironmentObject 从环境中读取自定义值
SwiftUI 的环境可以让我们从外部源中获取值,这对于读取 Core Data 上下文和视图的 presentation mode 来说很有用。 不过这里我们将把自定义对象注入环境中,然后稍后再读取出来。通过这种方式,我们能在复杂应用之中轻易地共享数据。
你已经了解过 @State 是如何用于单一视图的本地状态,以及如何把 @ObservedObject 从一个视图传递到另一个视图。 @EnvironmentObject 比它们都更进一步:我们可以把一个对象注入环境,以便任何子视图都可以自动获得该对象的访问能力。
想象我们的应用中有多个视图,所有视图串成一根链条:视图 A 显示 B,视图 B 显示视图 C,C 显示 D,D 显示 E。视图 A 和 E 都需要访问同一个对象,但从 A 到 E 你需要经过 B,C 和 D,而它们都不关心那个对象。如果我们继续使用 @ObservedObject,那就不得不把对象传给每个视图,由它传给下一个视图,直到最后抵达视图 E。这就有点烦人了,因为 B,C 和 D 并不关心那个对象。借助 @EnvironmentObject,视图 A 可以对象放进环境中,视图 E 从环境中读出该对象,同时视图 B,C 和 D 并不知道发生了什么 —— 这样做就好多了。
关于环境变量的一个复杂点在于它依赖父子关系。一个视图能否访问环境变量取决于它的父视图。例如,如果视图 A 能够访问某个环境对象,而视图 B 是在视图 A 内部的 —— 也就是说,视图 B 是放在 A 的 body 属性里的 —— 那么视图 B 也能访问那个环境对象。换句话说,假如视图 A 是一个导航视图,然后所有的视图都是被压进它的导航栈里的,那么它们都能访问导航视图可以访问的环境对象。但假如视图 A 以 sheet 的形式呈现视图 B,那么它们之前并不会自动地共享环境数据,我们需要手工传递对象。Apple 把 sheet 的情况表述为一个 bug,我希望它在未来的 SwiftUI 版本中能被修复。
在演示代码之前我还有一点要告诉你:环境变量使用你之前学习过的 ObservableObject 协议,SwiftUI 会确保所有共享对象的视图在对象变化时作出更新。
好了,话不多说,让我们用代码来说明如何用环境对象在两个视图之间共享数据。首先,下面是要共享的基本数据:
class User: ObservableObject {
@Published var name = "Taylor Swift"
}
如你所见,这里用到了 ObservableObject 和 @Published,就像我们前面学习过的一样。
接下来我们要定义两个 SwiftUI 视图,并且使用上面那个类。@EnvironmentObject 属性包装器是说明属性的数据是来自环境,而不是在本地创建的:
struct EditView: View {
@EnvironmentObject var user: User
var body: some View {
TextField("Name", text: $user.name)
}
}
struct DisplayView: View {
@EnvironmentObject var user: User
var body: some View {
Text(user.name)
}
}
@EnvironmentObject 会在环境中自动查找一个 User 实例,并且把找到的结果放进 user 属性。注意:如果环境中找不到 User 实例,你的应用会崩溃。所以请千万小心。
我们在同一个地方使用这两个视图,然后把一个 User 实例发送给它们:
struct ContentView: View {
let user = User()
var body: some View {
VStack {
EditView().environmentObject(user)
DisplayView().environmentObject(user)
}
}
}
代码先写到这里 —— 你可以运行一下应用,然后修改文本框,看看下面的文本视图会发生什么。当然,因为我们是在单个视图里演示的,但是这样做的好处是你可以很容易看清采用环境对象对于数据共享的作用。
现在是,是考察你的理解的时候了。把 ContentView 改成下面这样:
VStack {
EditView()
DisplayView()
}
.environmentObject(user)
你会发现结果一样。我们现在是把 user 放到 ContentView 的环境中,但是因为 EditView 和 DisplayView 都是 ContentView 的子视图,所以它们自动继承了 ContentView 的环境。
现在,你可能会好奇 SwiftUI 是如何在 .environmentObject(user) 和 @EnvironmentObject var user: User 之间建立联系的?它怎么知道要对象正确地塞进哪个属性?
是这样的,你已经学习过字典对?一个类型存键,另一个类型存值。环境可以很有效地让我们以数据的类型本身作为键,该类型的实例作为值。这个做法一开始有点费解,你可以这么想:键是 Int,String 和 Bool 这样的东西,而值是像 5,“Hello” 和 true 这样的东西。也就是说,假如我们讲 “给我 Int”,那我们会得到 5。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~