【问题标题】:How do I access data from a child view as the parent view at any time in SwiftUI?如何在 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)
                    }
                }
            }
        }
    }
}

【问题讨论】:

    标签: swiftui combine


    【解决方案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 收集数据,然后将其交给另一个视图。

      【讨论】:

        猜你喜欢
        • 2020-11-22
        • 1970-01-01
        • 2013-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多