如何在 SwiftUI 中随时从作为父视图的子视图访问数据?

Jng*_*n22 5 swiftui combine

我是 SwiftUI 的新手,并且知道我可能需要以某种方式实现 EnvironmentObject,但我不确定在这种情况下如何实现。

这是Trade班级

class Trade {
    var teamsSelected: [Team]

    init(teamsSelected: [Team]) {
        self.teamsSelected = teamsSelected
    }
}
Run Code Online (Sandbox Code Playgroud)

这是子视图。它有一个trade来自Trade类的实例。有一个按钮将 1 附加到 array teamsSelected

struct TeamRow: View {
    var trade: Trade

    var body: some View {
        Button(action: {
            self.trade.teamsSelected.append(1)
        }) {
            Text("Button")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是父视图。如您所见,我trade进入了子视图TeamRow。我想trade是在同步与tradeTeamRow这样我就可以再通trade.teamsSelectedTradeView

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)
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

gra*_*ell 9

我已经采取了你的代码,并改变了一些东西来说明SwiftUI是如何工作的,为了让大家更好的了解如何使用ObservableObject@ObservedObject@State,和@Binding

有一件事面前提起了-@ObservedObject试图运行iOS测试版13 6,7台物理设备上运行SwiftUI代码时,正在打破,或8我回答了问题,这里是进入,在更多的细节和说明如何使用@EnvironmentObject作为一个解决方法。


我们先来看看Trade。由于您希望Trade在视图之间传递一个对象,更改该Trade对象的属性,然后将这些更改反映在使用该Trade对象的每个视图中,因此您需要创建Trade一个ObservableObject. 我在您的Trade类中添加了一个额外的属性,纯粹是为了说明目的,稍后我会解释。我将向您展示两种编写 anObservableObject的方法 - 首先查看其工作原理的详细方式,然后是简洁的方式。

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
    }
}
Run Code Online (Sandbox Code Playgroud)

当我们符合 时ObservableObject,我们可以选择编写我们自己的ObservableObjectPublisher,我通过导入Combine和创建PassthroughSubject. 然后,当我想发布我的对象即将更改时,我可以self.objectWillChange.send()像之前一样调用willSetfornameteamsSelected

但是,此代码可以显着缩短。ObservableObject自动合成一个对象发布者,所以我们实际上不必自己声明它。我们还可以使用@Published来声明我们应该发送发布者事件而不是使用self.objectWillChange.send()in 的属性willSet

import SwiftUI

class Trade: ObservableObject {
    @Published var name: String
    @Published var teamsSelected: [Int]

    init(name: String, teamsSelected: [Int]) {
        self.name = name
        self.teamsSelected = teamsSelected
    }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们看看你的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)
            }))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
struct TeamRow: View {
    @ObservedObject var trade: Trade

    var body: some View {
        Button(action: {
            self.trade.teamsSelected.append(1)
        }) {
            Text("Button")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们先来看看@State var teams。我们@State用于简单值类型 - IntString、基本structs- 或简单值类型的集合。@ObservedObject用于符合 的对象,ObservableObject我们将其用于比Intor更复杂的数据结构String

您会注意到@State var teams,我添加了一个导航栏项目,teams当按下该项目时,它将向数组追加一个新项目,并且由于我们List是通过遍历该teams数组生成的,因此我们的视图会重新呈现并将新项目添加到我们List只要按下按钮。这是一个非常基本的示例,说明您将如何使用@State.

接下来,我们有我们的@ObservedObject var trade. 你会注意到我并没有真正做任何与你最初不同的事情。我仍在创建我的Trade类的一个实例并在我的视图之间传递该实例。但是由于它现在是ObservableObject,并且我们正在使用@ObservedObject,因此我们的视图现在将在Trade对象更改时都接收发布者事件,并将自动重新呈现它们的视图以反映这些更改。

我想指出的最后一件事是TextField我创建的TradeView更新Trade对象的name属性。

TextField("Trade Name", text: self.$trade.name)
Run Code Online (Sandbox Code Playgroud)

$字符表示我正在将绑定传递到文本字段。这意味着所做的任何更改TextFieldname将反映在我的Trade对象中。@Binding当您尝试在视图之间同步状态而不传递整个对象时,您可以通过声明允许您在视图之间传递绑定的属性来自己做同样的事情。

当我将您更改TradeView为 take 时@ObservedObject var trade,您可以简单地将teamsSelected您的交易视图作为这样的绑定传递给您TradeView(teamsSelected: self.$trade.teamsSelected)- 只要您TradeView接受一个绑定。要将您配置TradeView为接受绑定,您所要做的就是像这样声明您的teamsSelected属性TradeView

@Binding var teamsSelected: [Team]
Run Code Online (Sandbox Code Playgroud)

最后,如果您@ObservedObject在物理设备上使用时遇到问题,您可以在此处参考我的回答,以了解如何将其@EnvironmentObject用作解决方法。