【问题标题】:Passing An ObservedObject To Nested Child Views SwiftUI (SwiftUI Data Flow)将 ObservedObject 传递给嵌套子视图 SwiftUI(SwiftUI 数据流)
【发布时间】:2019-12-24 08:20:30
【问题描述】:

我试图理解为什么传递 @ObservedObject 变量不适用于嵌套子视图。可以传递数据,但更改仅反映在创建 @ObservedObject 变量的根视图中。更改不会显示在子视图中。查看 Apple 文档(已针对 Xcode beta 5 进行了更新),答案似乎是创建环境对象和常规变量,以便从环境对象中获取正确的索引。这里是苹果example

我的理解是@ObservedObject 可用于从多个视图引用变量,但如果您希望可以从任何视图访问数据,那么您应该使用环境对象。因此,我相信通过@ObservedObject 应该是可能的。我认为正在发生的问题是,由于 ScreenTwo 正在将 @Binding 变量传递给 DetailsView,这就是导致问题的原因。为了解决这个问题,我认为你需要继续传递完整的@ObservedObject,但是你需要某种类型的常规变量来获得正确的索引。

我觉得所有这些都应该更直接。

import SwiftUI
import Combine

struct Sport: Identifiable{
    var id = UUID()
    var name : String
    var isFavorite = false
    var school : String
}

final class SportData: ObservableObject  {
    @Published var store =
        [
            Sport(name: "soccer", isFavorite: false, school: "WPI"),
            Sport(name: "tennis", isFavorite: false, school: "WPI"),
            Sport(name: "swimming", isFavorite: true, school: "WPI"),
            Sport(name: "running", isFavorite: true, school: "RIT"),
    ]
}

struct Testing: View {
    @ObservedObject var sports = SportData()

    var body: some View {
        NavigationView{
            List{
                ForEach($sports.store){ sport in
                    NavigationLink(destination: ScreenTwo(sport: sport)){
                        HStack {
                            Text(sport.value.name)
                            Spacer()
                            Text(sport.value.isFavorite.description)
                        }
                    }
                }
            }
        }.navigationBarTitle("Settings")
    }
}

struct ScreenTwo : View{
    @Binding var sport : Sport

    var body: some View{
        NavigationLink(destination: DetailsView(sport: $sport)){
            Text(sport.isFavorite.description)
        }
    }
}

struct DetailsView: View {
    @Binding var sport : Sport

    var body: some View {
        Button(action: {
            self.sport.isFavorite.toggle()
            self.sport.name = "Ricky"
        }) {
            Text(sport.isFavorite.description)
            Text(sport.name)
        }
    }
}



#if DEBUG
struct Testing_Previews: PreviewProvider {
    static var previews: some View {
        Testing()
    }
}
#endif

【问题讨论】:

  • 这里似乎工作正常。首先选择 Ricky,然后通过单击 false 选择第二个 NavigationLink,然后它会显示一个按钮,该按钮会立即更改除了 Ricky 之外的 false 和一旦按下右侧的 false。还是第二个 NavigationLink 不需要?
  • 我不确定我是否遵循您的逻辑。以下是当前发生的情况: 1. 按第一行的“足球” 2. 单击导航链接 3. 单击“假足球”按钮。 4. 一直到根视图,第一行将是“Ricky true”。应该发生的是,在第 3 步,按钮应立即更改为“Ricky true”,然后当您返回第二个屏幕时,它应该显示“true”。根屏幕应显示“Ricky true”。
  • 啊,现在我明白你的意思了。是的,这看起来很奇怪。好像 SwiftUI 不想重绘destination: 中的内容。尽管我之前说过,它不会改变destination:,只有重绘才能做到这一点。
  • 没错。它没有产生预期的行为。这是一个错误,还是一些我不明白的更深层次的逻辑我不确定。
  • 我也注意到了。但是,不要认为这是因为 NavigationLink。我认为这更多地与 ObservedObject 是变量的真实来源这一事实有关,而 Binding 只是对它的引用。因此,当 ScreenTwo 将 Binding 传递给另一个 Binding 时,我认为这会导致问题。在我所做的所有阅读和我看过的示例中,没有人像这样传递变量。它们只将 ObservedObject 传递给单个 Binding。该绑定永远不会传递给其他任何东西。这让我很困惑,因为这似乎是 ObservedObject 的重点。

标签: xcode nested observable swiftui


【解决方案1】:

对于ObservableObject 配对ObservedObject 使视图刷新,所以要解决有问题的任务,我会推荐以下方法:

演示

代码

import SwiftUI
import Combine

class Sport: ObservableObject, Hashable, Identifiable {

    static func == (lhs: Sport, rhs: Sport) -> Bool {
        lhs.name == rhs.name && lhs.isFavorite == rhs.isFavorite && lhs.school == rhs.school
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(isFavorite)
        hasher.combine(school)
    }

    @Published var name : String
    @Published var isFavorite = false
    @Published var school : String

    init(name: String, isFavorite: Bool, school: String) {
        self.name = name
        self.isFavorite = isFavorite
        self.school = school
    }
}

final class SportData: ObservableObject  {
    @Published var store =
        [
            Sport(name: "soccer", isFavorite: false, school: "WPI"),
            Sport(name: "tennis", isFavorite: false, school: "WPI"),
            Sport(name: "swimming", isFavorite: true, school: "WPI"),
            Sport(name: "running", isFavorite: true, school: "RIT"),
    ]
}

struct TestingObservedObject: View {
    @ObservedObject var sports = SportData()

    var body: some View {
        NavigationView{
            List{
                ForEach(sports.store){ sport in
                    NavigationLink(destination: ScreenTwo(sport: sport)) {
                        HStack {
                            Text("\(sport.name)")
                            Spacer()
                            Text(sport.isFavorite.description)
                        }
                    }
                    .onReceive(sport.$isFavorite) { _ in self.sports.objectWillChange.send() }
                }
            }
        }.navigationBarTitle("Settings")
    }
}

struct ScreenTwo : View{
    @ObservedObject var sport : Sport

    var body: some View{
        NavigationLink(destination: DetailsView(sport: sport)){
            Text(sport.isFavorite.description)
        }
    }
}

struct DetailsView: View {
    @ObservedObject var sport : Sport

    var body: some View {
        Button(action: {
            self.sport.isFavorite.toggle()
            self.sport.name = "Ricky"
        }) {
            Text(sport.isFavorite.description)
            Text(sport.name)
        }
    }
}



#if DEBUG
struct Testing_Previews: PreviewProvider {
    static var previews: some View {
        TestingObservedObject()
    }
}
#endif

【讨论】:

  • 我猜想用@ObservedObject 而不是@Binding 调用ScreenTwo 的init 会得到一个sport not reference 的副本,对吗?
  • @JAHelia,不,运动是通过引用到处传递的。
  • @JAHelia ObservedObjectstruct,所以这很有可能。
  • 使用@EnvironmentObject 不是最好的吗?
【解决方案2】:

您应该将运动场定义为@EnviromentObject,而不是ScreenTwoDetailsView 上的@ObservedObject。还在TestingObservedObject 视图上使用NavigationLink(destination: ScreenTwo()).environmentObject(sport) 设置环境对象。

【讨论】:

  • 感谢您的回答。但是,我对此有一个疑问:我的Root View 中有一个Fetch RequestComputed Property 将使用.environmentObject(_:) 指定将哪些结果传递到视图层次结构中。由于Fetch Request 的结果可以更改,因此Computed Property 返回另一个结果,所有子视图都会自动更新吗?
【解决方案3】:

@EnvironmentObject - 由父视图或祖先视图提供的可观察对象的属性包装器类型。

environmentObject(_:) - 将 ObservableObject 提供给视图子层次结构。(这是一个 viewModifire)

这样我们就可以通过environmentObject(_:)分享@ObservableObject。要接受它,子视图应该有 @EnvironmentObject (ex :- @EnvironmentObject var viewModel: MyViewModel)

【讨论】:

  • 感谢您的回答。但是,我对此有一个疑问:我的Root View 中有一个Fetch RequestComputed Property 将使用.environmentObject(_:) 指定将哪些结果传递到视图层次结构中。由于Fetch Request 的结果可以更改,因此Computed Property 返回另一个结果,所有子视图都会自动更新吗?
猜你喜欢
  • 2020-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多