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

时间:2019-08-22 21:54:46

标签: swiftui combine

我是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中。我希望tradetrade中的TeamRow保持同步,以便随后将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)
                    }
                }
            }
        }
    }
}

3 个答案:

答案 0 :(得分:3)

我已采用您的代码,并做了一些更改以说明SwiftUI的工作方式,以便使您更好地了解如何使用ObservableObject@ObservedObject@State和{ {1}}。

首先要提一件事-尝试在运行iOS 13 Beta 6、7或8的物理设备上运行SwiftUI代码时,@Binding目前已损坏。我回答了一个问题here对此进行了更详细的介绍,并说明了如何使用@ObservedObject作为解决方法。


首先让我们看一下@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中使用self.objectWillChange.send()

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 } } TeamSelectViewTeamRow。再次记住,我做了一些更改(并添加了一个示例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用于简单值类型-@StateInt,基本String-或简单值类型的集合。 structs用于符合@ObservedObject的对象,我们将其用于比ObservableObjectInt更复杂的数据结构。

使用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) 字符表示我正在将绑定传递给文本字段。这意味着$TextField所做的任何更改都将反映在我的name对象中。您可以声明Trade属性来自己做同样的事情,这些属性允许您在尝试在视图之间同步状态而不传递整个对象时在视图之间传递绑定。

虽然我将您的@Binding更改为TradeView,但您可以将@ObservedObject var trade像这样的绑定传递给您的交易视图-teamsSelected-只要您的{ {1}}接受绑定。要将TradeView(teamsSelected: self.$trade.teamsSelected)配置为接受绑定,只需要做的就是在TradeView中声明您的TradeView属性,如下所示:

teamsSelected

最后,如果在物理设备上使用TradeView时遇到问题,可以参考我的答案here来获得有关如何使用@Binding var teamsSelected: [Team] 的解决方法的说明。

答案 1 :(得分:0)

首先,非常感谢Graycampbell给了我更好的理解!但是,我的理解似乎还没有完全解决。我的情况略有不同,我无法完全解决。

我已经在一个单独的线程中问过我的问题,但是我也想在这里添加它,因为它在某种程度上适合主题:Reading values from list of toggles in SwiftUI

也许你们当中有人可以帮助我。与本主题的初始帖子的主要区别在于,我必须从GameGenerationRow中的每个GameGenerationView收集数据,然后将其移交给另一个视图。

答案 2 :(得分:0)

您可以在合并中使用@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(@Publilshed)中具有@ObservedObject属性,则可以使用它来绑定ChildView(property1: $viewModel.publishedProperty)之类的状态。