如何在 SwiftUI 中随时从子视图作为父视图访问数据?
Posted
技术标签:
【中文标题】如何在 SwiftUI 中随时从子视图作为父视图访问数据?【英文标题】:How do I access data from a child view as the parent view at any time in SwiftUI? 【发布时间】:2019-08-22 21:54:46 【问题描述】:我是 SwiftUI 新手,知道我可能需要以某种方式实现 EnvironmentObject,但我不确定在这种情况下如何实现。
这是Trade
类
class Trade
var teamsSelected: [Team]
init(teamsSelected: [Team])
self.teamsSelected = teamsSelected
这是子视图。它有一个来自Trade
类的实例trade
。有一个按钮将 1 附加到数组 teamsSelected
。
struct TeamRow: View
var trade: Trade
var body: some View
Button(action:
self.trade.teamsSelected.append(1)
)
Text("Button")
这是父视图。如您所见,我将trade
传递到子视图TeamRow
。我希望trade
与TeamRow
中的trade
同步,这样我就可以将trade.teamsSelected
传递给TradeView
。
struct TeamSelectView: View
var trade = Trade(teamsSelected: [])
var body: some View
NavigationView
VStack
NavigationLink(destination: TradeView(teamsSelected: trade.teamsSelected))
Text("Trade")
List
ForEach(teams) team in
TeamRow(trade: self.trade)
【问题讨论】:
【参考方案1】:我采用了您的代码并更改了一些内容来说明 SwiftUI 的工作原理,以便让您更好地了解如何使用 ObservableObject
、@ObservedObject
、@State
和 @Binding
。
首先要提一下 - @ObservedObject
当前在运行 ios 13 Beta 6、7 或 8 的物理设备上运行 SwiftUI 代码时出现故障。我回答了一个问题 here,该问题在更多详细说明并解释如何使用@EnvironmentObject
作为解决方法。
我们先来看看Trade
。由于您希望在视图之间传递Trade
对象,因此更改该Trade
对象的属性,然后将这些更改反映在使用该Trade
对象的每个视图中,您将需要制作Trade
一个ObservableObject
。我已经为您的 Trade
类添加了一个额外的属性,纯粹是为了说明,稍后我会解释。我将向您展示编写ObservableObject
的两种方法 - 首先是详细的方法,看看它是如何工作的,然后是简洁的方法。
import SwiftUI
import Combine
class Trade: ObservableObject
let objectWillChange = PassthroughSubject<Void, Never>()
var name: String
willSet
self.objectWillChange.send()
var teamsSelected: [Int]
willSet
self.objectWillChange.send()
init(name: String, teamsSelected: [Int])
self.name = name
self.teamsSelected = teamsSelected
当我们符合ObservableObject
时,我们可以选择编写自己的ObservableObjectPublisher
,我通过导入Combine
并创建PassthroughSubject
来完成。然后,当我想发布我的对象即将更改时,我可以调用self.objectWillChange.send()
,就像我在willSet
上的name
和teamsSelected
一样。
但是,此代码可以大大缩短。 ObservableObject
自动合成一个对象发布者,所以我们实际上不必自己声明它。我们还可以使用@Published
来声明应该发送发布者事件的属性,而不是在willSet
中使用self.objectWillChange.send()
。
import SwiftUI
class Trade: ObservableObject
@Published var name: String
@Published var teamsSelected: [Int]
init(name: String, teamsSelected: [Int])
self.name = name
self.teamsSelected = teamsSelected
现在让我们看看您的TeamSelectView
、TeamRow
和TradeView
。请再次记住,我做了一些更改(并添加了一个示例 TradeView
)只是为了说明一些事情。
struct TeamSelectView: View
@ObservedObject var trade = Trade(name: "Name", teamsSelected: [])
@State var teams = [1, 1, 1, 1, 1]
var body: some View
NavigationView
VStack
NavigationLink(destination: TradeView(trade: self.trade))
Text(self.trade.name)
List
ForEach(self.teams, id: \.self) team in
TeamRow(trade: self.trade)
Text("\(self.trade.teamsSelected.count)")
.navigationBarItems(trailing: Button("+", action:
self.teams.append(1)
))
struct TeamRow: View
@ObservedObject var trade: Trade
var body: some View
Button(action:
self.trade.teamsSelected.append(1)
)
Text("Button")
struct TradeView: View
@ObservedObject var trade: Trade
var body: some View
VStack
Text("\(self.trade.teamsSelected.count)")
TextField("Trade Name", text: self.$trade.name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
我们先来看看@State var teams
。我们将@State
用于简单值类型——Int
、String
、基本structs
——或简单值类型的集合。 @ObservedObject
用于符合ObservableObject
的对象,我们将其用于比Int
或String
更复杂的数据结构。
您会注意到@State var teams
是我添加了一个导航栏项目,当按下时,它将在teams
数组中附加一个新项目,因为我们的List
是通过迭代该@ 生成的987654370@ 数组,每当按下按钮时,我们的视图都会重新渲染并向我们的List
添加一个新项目。这是您如何使用@State
的一个非常基本的示例。
接下来,我们有我们的@ObservedObject var trade
。你会注意到我并没有真正做任何与你最初不同的事情。我仍在创建我的 Trade
类的实例并在我的视图之间传递该实例。但是由于它现在是ObservableObject
,并且我们使用的是@ObservedObject
,所以我们的视图现在都将在Trade
对象发生更改时接收发布者事件,并且会自动重新呈现它们的视图以反映这些更改。
我要指出的最后一件事是我在TradeView
中创建的TextField
以更新Trade
对象的name
属性。
TextField("Trade Name", text: self.$trade.name)
$
字符表示我正在将绑定传递给文本字段。这意味着TextField
对name
所做的任何更改都将反映在我的Trade
对象中。您可以通过声明 @Binding
属性来自己做同样的事情,当您尝试在视图之间同步状态而不传递整个对象时,这些属性允许您在视图之间传递绑定。
虽然我将您的 TradeView
更改为 @ObservedObject var trade
,但您可以简单地将 teamsSelected
作为绑定传递到您的交易视图 - TradeView(teamsSelected: self.$trade.teamsSelected)
- 只要您的 TradeView
接受绑定即可。要配置您的TradeView
以接受绑定,您所要做的就是在TradeView
中声明您的teamsSelected
属性,如下所示:
@Binding var teamsSelected: [Team]
最后,如果您在物理设备上使用@ObservedObject
时遇到问题,您可以参考我的回答here,了解如何使用@EnvironmentObject
作为解决方法。
【讨论】:
太棒了,解释得很好。【参考方案2】:您可以在组合中使用@Binding
和@State
/ @Published
。
换句话说,在子视图中使用@Binding
属性并将其与父视图中的@State
或@Published
属性绑定,如下所示。
struct ChildView: View
@Binding var property1: String
var body: some View
VStack(alignment: .leading)
TextField(placeholderTitle, text: $property1)
struct PrimaryTextField_Previews: PreviewProvider
static var previews: some View
PrimaryTextField(value: .constant(""))
struct ParentView: View
@State linkedProperty: String = ""
//...
ChildView(property1: $linkedProperty)
//...
或者,如果您的 viewModel(@ObservedObject
) 中有 @Publilshed
属性,则使用它来绑定状态,例如 ChildView(property1: $viewModel.publishedProperty)
。
【讨论】:
【参考方案3】:首先非常感谢 graycampbell 让我有了更好的理解!但是,我的理解似乎并不完全奏效。我有一个稍微不同的情况,我无法完全解决。
我已经在一个单独的帖子中提出了我的问题,但我也想在此处添加它,因为它在某种程度上符合主题:Reading values from list of toggles in SwiftUI
也许你们中的某个人可以帮助我解决这个问题。如果这个主题与最初的帖子的主要区别是,我必须从GameGenerationView
中的每个GameGenerationRow
收集数据,然后将其交给另一个视图。
【讨论】:
以上是关于如何在 SwiftUI 中随时从子视图作为父视图访问数据?的主要内容,如果未能解决你的问题,请参考以下文章