'Passing An ObservedObject To Nested Child Views SwiftUI (SwiftUI Data Flow)

I'm trying to understand why passing an @ObservedObject variable does not work for nested child views. The data is able to be passed but the changes only reflects at the root view where the @ObservedObject variable was created. The changes don't show in the subviews. Looking at Apple documentation (which has been updated for Xcode beta 5), the answer seems to be to create both an environment object and a regular variable in order to get the correct index from the environment object. Here is Apples example.

My understanding is that an @ObservedObject can be used to reference a variable from multiple views, but if you want the data to be accessible from any view then you should use an environment object. Therefore I believe that passing the @ObservedObject should be possible. The issue I believe is happening is that since ScreenTwo is passing the @Binding variable to DetailsView and that is what's causing a problem. To solve this I would think you need to keep passing the full @ObservedObject, but then again you would need some type of regular variable to get the proper index.

I feel like all of this should be much more straightforward.

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


Solution 1:[1]

For ObservableObject the pairing ObservedObject makes view refresh, so to solve the task in question I would recommend the following approach:

Demo

Usage of ObservingObject/ObjservedObject pattern

Code

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

backup

Solution 2:[2]

You should define sport field as @EnviromentObject instead of @ObservedObject on ScreenTwo and DetailsView. Also set enviroment object with NavigationLink(destination: ScreenTwo()).environmentObject(sport) on TestingObservedObject view.

Solution 3:[3]

@EnvironmentObject - A property wrapper type for an observable object supplied by a parent or ancestor view.

environmentObject(_:) - Supplies an ObservableObject to a view subhierachy.(this is a viewModifire)

So that we can share @ObservableObject through environmentObject(_:). To accept it, sub view should have @EnvironmentObject (ex :- @EnvironmentObject var viewModel: MyViewModel)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 np2314
Solution 3 Yodagama