如何在SwiftUI中随时从子视图作为父视图访问数据? [英] How do I access data from a child view as the parent view at any time in SwiftUI?

查看:136
本文介绍了如何在SwiftUI中随时从子视图作为父视图访问数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是SwiftUI的新手,了解可能需要以某种方式实现EnvironmentObject,但是我不确定在这种情况下如何实现.

I'm new to SwiftUI and understand that I may need to implement EnvironmentObject in some way, but I'm not sure how in this case.

这是 Trade

class Trade {
    var teamsSelected: [Team]

    init(teamsSelected: [Team]) {
        self.teamsSelected = teamsSelected
    }
}

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

This is the child view. It has an instance trade from the Trade class. There is a button that appends 1 to array 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 .

This is the parent view. As you can see, I pass trade into the child view TeamRow. I want trade to be in sync with trade in TeamRow so that I can then pass trade.teamsSelected to 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的工作方式,以便使您更好地了解如何使用 ObservableObject @ObservedObject @State @Binding .

I've taken your code and changed some things to illustrate how SwiftUI works in order to give you a better understanding of how to use ObservableObject, @ObservedObject, @State, and @Binding.

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

One thing to mention up front - @ObservedObject is currently broken when trying to run SwiftUI code on a physical device running iOS 13 Beta 6, 7, or 8. I answered a question here that goes into that in more detail and explains how to use @EnvironmentObject as a workaround.

首先让我们看一下 Trade .由于您要在视图之间传递 Trade 对象,因此请更改该 Trade 对象的属性,然后将这些更改反映在使用该 Trade的每个视图中对象,您需要将 Trade 设置为 ObservableObject .我已经为您的 Trade 类添加了一个额外的属性,纯粹出于说明目的,我将在后面解释.我将向您展示两种编写 ObservableObject 的方法-首先通过冗长的方式查看其工作方式,然后通过简洁的方式来实现.

Let's first take a look at Trade. Since you're looking to pass a Trade object between views, change properties on that Trade object, and then have those changes reflected in every view that uses that Trade object, you'll want to make Trade an ObservableObject. I've added an extra property to your Trade class purely for illustrative purposes that I'll explain later. I'm going to show you two ways to write an ObservableObject - the verbose way first to see how it works, and then the concise way.

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 .然后,当我要发布对象即将更改时,可以像在 name teamsSelected .

When we conform to ObservableObject, we have the option to write our own ObservableObjectPublisher, which I've done by importing Combine and creating a PassthroughSubject. Then, when I want to publish that my object is about to change, I can call self.objectWillChange.send() as I have on willSet for name and teamsSelected.

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

This code can be shortened significantly, however. ObservableObject automatically synthesizes an object publisher, so we don't actually have to declare it ourselves. We can also use @Published to declare our properties that should send a publisher event instead of using 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
    }
}

现在,让我们看一下您的 TeamSelectView TeamRow TradeView .再次记住,我做了一些更改(并添加了一个示例 TradeView ),只是为了说明一些事情.

Now let's take a look at your TeamSelectView, TeamRow, and TradeView. Keep in mind once again that I've made some changes (and added an example TradeView) just to illustrate a couple of things.

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团队.我们将 @State 用于简单值类型- Int String ,基本 struct -或简单值类型的集合. @ObservedObject 用于符合 ObservableObject 的对象,我们将其用于比 Int String更复杂的数据结构.

Let's first look at @State var teams. We use @State for simple value types - Int, String, basic structs - or collections of simple value types. @ObservedObject is used for objects that conform to ObservableObject, which we use for data structures that are more complex than just Int or String.

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

What you'll notice with @State var teams is that I've added a navigation bar item that will append a new item to the teams array when pressed, and since our List is generated by iterating through that teams array, our view re-renders and adds a new item to our List whenever the button is pressed. That's a very basic example of how you would use @State.

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

Next, we have our @ObservedObject var trade. You'll notice that I'm not really doing anything different than you were originally. I'm still creating an instance of my Trade class and passing that instance between my views. But since it's now an ObservableObject, and we're using @ObservedObject, our views will now all receive publisher events whenever the Trade object changes and will automatically re-render their views to reflect those changes.

我要指出的最后一件事是我在 TradeView 中创建的 TextField ,以更新 Trade 对象的名称属性.

The last thing I want to point out is the TextField I created in TradeView to update the Trade object's name property.

TextField("Trade Name", text: self.$trade.name)

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

The $ character indicates that I'm passing a binding to the text field. This means that any changes TextField makes to name will be reflected in my Trade object. You can do the same thing yourself by declaring @Binding properties that allow you to pass bindings between views when you are trying to sync state between your views without passing entire objects.

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

While I changed your TradeView to take @ObservedObject var trade, you could simply pass teamsSelected to your trade view as a binding like this - TradeView(teamsSelected: self.$trade.teamsSelected) - as long as your TradeView accepts a binding. To configure your TradeView to accept a binding, all you would have to do is declare your teamsSelected property in TradeView like this:

@Binding var teamsSelected: [Team]

最后,如果在物理设备上使用 @ObservedObject 时遇到问题,则可以参考我的答案

And lastly, if you run into issues with using @ObservedObject on a physical device, you can refer to my answer here for an explanation of how to use @EnvironmentObject as a workaround.

这篇关于如何在SwiftUI中随时从子视图作为父视图访问数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆