如何在 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。我希望tradeTeamRow 中的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 上的nameteamsSelected 一样。

但是,此代码可以大大缩短。 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
    

现在让我们看看您的TeamSelectViewTeamRowTradeView。请再次记住,我做了一些更改(并添加了一个示例 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 用于简单值类型——IntString、基本structs——或简单值类型的集合。 @ObservedObject 用于符合ObservableObject 的对象,我们将其用于比IntString 更复杂的数据结构。

您会注意到@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)

$ 字符表示我正在将绑定传递给文本字段。这意味着TextFieldname 所做的任何更改都将反映在我的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 中随时从子视图作为父视图访问数据?的主要内容,如果未能解决你的问题,请参考以下文章

从子视图 SwiftUI 中删除 UI 元素

SwiftUI:从子视图中关闭模式

SwiftUI 视图 onAppear 事件调用回扫手势动作

SwiftUI NavigationView 切换色调颜色

绑定视图 - 如何从子列表中获取父属性的详细信息

如何从子视图控制器访问父视图控制器的视图?