使用 SwiftUI 的环境传递标准类型(Int、String 等)来查看层次结构

Riv*_*era 1 swift swiftui

我知道我们可以ObservableObject使用传递自定义类型.environmentObject(_:),然后使用@EnvironmentObject特殊属性从子视图访问它。

但是我们想要在视图周围传递非自定义的标准属性什么IntString

我能看到的唯一候选人是:

func environment<V>(_ keyPath: WritableKeyPath<EnvironmentValues, V>, _ value: V) -> some View

但它似乎只适用于固定的、非自定义的,KeyPath例如\.colorScheme.

换句话说,我希望传递@State使用环境。

Joh*_* M. 5

长话短说

@State被设计为在单个视图中使用(或传递Binding到后代视图)。避免ObservableObject为每个视图创建一个类只是为了方便。但是,如果您将数据传递到不紧密连接的多个视图,则应该将数据包装在一个ObservableObject类中并使用.environmentObject()

解释

为了详细说明 krjw 的评论以及为什么不能传递@State属性,我们来谈谈@State@ObservedObject@EnvironmentObject@Published@Binding属性包装器实际上在做什么。

因为您的视图是一个结构体,所以它是一个值类型。当应用程序的状态发生变化并且视图必须以不同的方式显示时,当前的视图结构将被丢弃,并在其位置创建一个新的视图结构。但这意味着数据不能存储在结构体中,因为每次视图更改时数据都会被丢弃并被初始值替换。

输入ObservableObject协议。当您创建一个符合此协议的对象并标记其属性之一时@Published,这只是一个很好的语法糖,可以说“我已经有了这个引用对象,我打算用它来表示状态;请设置一些组合发布者如有变化随时广播。” 因为它是引用类型,所以它可以位于内存中的某个位置,在我们的视图之外,只要我们的视图引用它并订阅它的发布者,它们就可以知道它何时更改,并相应地刷新。

但请记住,我们说过我们的视图是结构。即使您为它们的其中一个属性分配了对 的引用ObservableObject并订阅了其发布者,每当重新创建结构时我们都会丢失该信息。这就是@ObservedObject包装器的用武之地。实际上,所做的就是说,“在我的结构之外存储对此对象的引用和订阅,并且当重新创建我的视图时,确保我知道我应该引用和监听这个物体。”

@EnvironmentObject做同样的事情,它只是一个很好的语法糖,可以避免必须传递ObservedObjectvia 初始值设定项。相反,它说:“相信我,你的祖先会有一个这种类型的物品。只要去向他们要就可以了。”

所以说真的,这足以完成我们在 SwiftUI 中需要做的所有事情。但是,如果我只需要存储和观察Bool更改,并且只需要在单个视图中怎么办?仅仅为此创建一个完整的类类型有点重。这就是属性包装器的用武之地。它基本上只是将/ stuff@State的功能与stuff 结合起来。它说:“将这些数据保留在我的结构内存之外(这样它就不会在刷新时被丢弃),并在发生任何变化时通知我。” 这就是为什么您可以修改属性,即使修改结构的属性应该重新创建该结构;数据并不真正位于您的结构内部。ObservedObject@Published@ObservedObject@State

@State只是一种简洁、方便的方法来获取ObservableObject带有@ObservedObject包装器的类的功能,该包装器仅在一个结构中使用。

为了完整起见,我们来谈谈@Binding. 有时,我们视图的直接后代可能需要修改@State其祖先的变量(例如,TextField需要修改String其父视图持有的值)。属性@Binding包装器只是意味着,“您不需要存储这些数据(因为它已经由另一个视图/对象存储);您只需要查看它的更改并能够将更改写回。”

注意:是的,在幕后,@State并不是字面上建立一个符合的类ObservableObject和另一个@ObservedObject包装器;它比那更有效率。但从功能上来说,从使用的角度来看,这就是正在发生的事情。