正如标题所示,我正在尝试将数据从一个子视图(A)通过父视图(P)传递到另一个子视图(B)。
父视图如下:
@State var rectFrame: CGRect = .zero
var body: some View {
childViewA(rectFrame: $rectFrame)
childViewB()
}
其中childViewA
获得CGRect
需要的childViewB
。
childViewA
看起来像这样:
@Binding var rectFrame: CGRect
var body: some View {
// Very long code where rectFrame is obtained and printed to console correctly
}
如何将rectFrame
传递给childViewB
?尽管在CGRect.zero
和childViewB
中都打印了正确的值,但到目前为止,我尝试过的所有操作都在parentView
中返回了childViewA
。
为了尝试将值传递给childViewB
,我像这样重写了parentView
:
@State var rectFrame: CGRect = .zero
var body: some View {
childViewA(rectFrame: $rectFrame)
childViewB(rectFrame: $rectFrame.value)
}
childViewB
具有以下结构:
var rectFrame: CGRect = CGRect.zero
var body: some View {
}
但是每次只打印CGRect.zero
。
我最近尝试过@ObjectBinding
,但是我一直在努力,所以如果有人可以帮助我解决这个特定示例,我将非常感激。
class SourceRectBindings: BindableObject {
let willChange = PassthroughSubject<Void, Never>()
var sourceRect: CGRect = .zero {
willSet {
willChange.send()
}
}
}
答案 0 :(得分:2)
您可以将EnvironmentObject用于此类...
EnvironmentObject的好处是,无论何时何地更改其中一个变量,它都将始终确保在使用该变量的每个位置都对其进行更新。
注意:要获取每个变量的更新,必须确保在希望对其进行更新的每个View上传递BindableObject类/ EnvironmentObject。
SourceRectBindings:
class SourceRectBindings: BindableObject {
/* PROTOCOL PROPERTIES */
var willChange = PassthroughSubject<Application, Never>()
/* Seems Like you missed the 'self' (send(self)) */
var sourceRect: CGRect = .zero { willSet { willChange.send(self) }
}
父母:
struct ParentView: view {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
childViewA()
.environmentObject(sourceRectBindings)
childViewB()
.environmentObject(sourceRectBindings)
}
}
ChildViewA:
struct ChildViewA: view {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
// Update your frame and the environment...
// ...will update the variable everywhere it was used
self.sourceRectBindings.sourceRect = CGRect(x: 0, y: 0, width: 300, height: 400)
return Text("From ChildViewA : \(self.sourceRectBindings.sourceRect)")
}
}
ChildViewB:
struct ChildViewB: view {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
// This variable will always be up to date
return Text("From ChildViewB : \(self.sourceRectBindings.sourceRect)")
}
}
完成工作的最后步骤
•进入最高视图,您想要更新变量,这是您的父视图,但是我通常更喜欢我的 SceneDelegate ...因此,这是我通常的做法:
let sourceRectBindings = SourceRectBindings()
•然后在同一类“ SceneDelegate”中,转到我指定我的根视图并通过我的EnviromentObject的地方
window.rootViewController = UIHostingController(
rootView: ContentView()
.environmentObject(sourceRectBindings)
)
•然后,在最后一步中,我将其传递到May Parent视图
struct ContentView : View {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
ParentView()
.environmentObject(sourceRectBindings)
}
}
如果您像这样运行代码,您将看到预览引发错误。那是因为它没有传递环境变量。 为此,只需启动您的环境实例之类的虚拟对象即可:
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(SourceRectBindings())
}
}
#endif
现在,您可以随意使用该变量,并且如果要传递除rect外的任何其他内容,只需将变量添加到EnvironmentObject类中,就可以访问和更新该变量
答案 1 :(得分:1)
您没有提到ChildViewB如何获取其文本。也就是说,它是否使用GeometryReader或任何其他来源。无论使用哪种方法,都可以通过两种方式将信息向上传递给层次结构:通过绑定或通过首选项。而且,如果涉及几何图形,甚至锚定首选项。
对于第一种情况,在未看到实现的情况下,设置rect时可能会缺少DispatchQueue.main.async {}
。这是因为更改必须在视图完成自身更新之后 之后进行。但是,我认为这是一个肮脏的技巧,只能在测试时使用,而绝不会在生产代码上使用。
第二种选择(使用首选项)是一种更可靠的方法(首选;-)。我将包括两种方法的代码,但是您应该明确地了解有关首选项的更多信息。我最近写了一篇文章,您可以在这里找到:https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
第一种方法:我不建议将几何值向上传递到层次结构中,这将影响其他视图的绘制方式。但是,如果您坚持要这样做,那么可以通过以下方法来实现它:
struct ContentView : View {
@State var rectFrame: CGRect = .zero
var body: some View {
VStack {
ChildViewA(rectFrame: $rectFrame)
ChildViewB(rectFrame: rectFrame)
}
}
}
struct ChildViewA: View {
@Binding var rectFrame: CGRect
var body: some View {
DispatchQueue.main.async {
self.rectFrame = CGRect(x: 10, y: 20, width: 30, height: 40)
}
return Text("CHILD VIEW A")
}
}
struct ChildViewB: View {
var rectFrame: CGRect = CGRect.zero
var body: some View {
Text("CHILD VIEW B: RECT WIDTH = \(rectFrame.size.width)")
}
}
第二种方法:使用首选项
import SwiftUI
struct MyPrefKey: PreferenceKey {
typealias Value = CGRect
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
struct ContentView : View {
@State var rectFrame: CGRect = .zero
var body: some View {
VStack {
ChildViewA()
ChildViewB(rectFrame: rectFrame)
}
.onPreferenceChange(MyPrefKey.self) {
self.rectFrame = $0
}
}
}
struct ChildViewA: View {
var body: some View {
return Text("CHILD VIEW A")
.preference(key: MyPrefKey.self, value: CGRect(x: 10, y: 20, width: 30, height: 40))
}
}
struct ChildViewB: View {
var rectFrame: CGRect = CGRect.zero
var body: some View {
Text("CHILD VIEW B: RECT WIDTH = \(rectFrame.size.width)")
}
}
答案 2 :(得分:0)
我希望我已正确理解您的问题,即在两个孩子之间共享信息。我采用的方法是在两个共享相同状态的子代中都有@Binding变量,即使用$ rectFrame实例化它们。这样,两者将始终具有相同的值,而不是实例化时的值。
在我的情况下,这是一个在一个子节点中更新并在另一个子节点中显示的数组,因此SwiftUI负责对更改做出反应。也许你的情况不是那么简单。