UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Help with EnvironmentObject Published property updating view

Forums > SwiftUI

@TC72  

I'm using a class which is ObservableObject to publish data used by several views in my App. There's a strange issue where I call a function to change a value which doesn't get updated in the view. I've tested using another @State variable just called "update" to force an update and that works.

OK, not explaining it well but here's what I have:

Class A - ObseravbleObject Published is an Array of Class B not an ObservableObject

At the top of my App I create ObjectA which is of Class A and populate the array with Class B objects and use .environmentObject( ObjectA )

View A has @EnvironmentObject ObjectA.

View B and View C are children of View A. View B displays a variable from the Array of Class B. View C has a button which when pressed changes the variable via a method of Class B.

When I hit the button nothing changes, if I get the view to redraw then View B shows the new value. I've tried making Class B an ObservableObject as well and Publishing the variable but it still doesn't work.

I guess having Class B inside an array means Class A can't see the change and tell the views to redraw. Any idea what's the right way to do this?

2      

hi,

visual update issues are more common than you think, and there's probably no one right way to get updates done right in the situation you describe.

here's one possible solution:

  • the ClassA object is an ObservableObject and marks its array as @Published. this means that if you change the array, some View will know about it.
  • the Class B object that is the variable in View B should be marked as @ObservedObject so that a change to (one of) its properties gets View B redrawn. so make Class B conform to ObservableObject, and mark appropriate properties as @Published.
  • however, changes to a Class B object are not known to Class A because changing a property in a Class B object makes no change to the array of objects in Class A.
  • one can fix this by using Combine so that Class A subscribes to each class B object in its array, being sure that any objectWillChange message received from a Class B object is relayed as an objectWillChange message from the Class A object.

here's a relevant example: the main view shows the list of items held by a Class A object and navigates to sort of a detail view to show and edit information for one of the Class B objects

import SwiftUI
import Combine

class ClassB: ObservableObject, Identifiable {
    var id = UUID()
    var name: String
    @Published var age: Int // in this demo, i only care about age changes
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

class ClassA: ObservableObject {
    @Published var items = [ClassB]()
    private var cancellables = Set<AnyCancellable>()

    func addItem(_ item: ClassB) {
        items.append(item)
        // this subscribes us to listen for objectWillChange messages from each
        // of the items in the array, and we emit our own objectWillChange message
        item.objectWillChange
            .sink(receiveValue: { self.objectWillChange.send() })
            .store(in: &cancellables)
    }
}

struct ContentView: View {

    @ObservedObject private var object = ClassA()
    @State private var objectLoaded = false
    var body: some View {
        NavigationView {
            List(object.items) { item in
                NavigationLink(destination: BObjectView(bObject: item)) {
                    HStack {
                        Text(item.name)
                        Spacer()
                        Text("\(item.age)")
                    }
                }
            }
            .onAppear(perform: loadObject)
            .navigationBarTitle("Main")
        }
    }
    // i loaded the array directly here, did not pass it in through the environment,
    // but the effect should be the same
    func loadObject() {
        if !objectLoaded {
            object.addItem(ClassB(name: "John", age: 30))
            object.addItem(ClassB(name: "Sally", age: 32))
            object.addItem(ClassB(name: "Mike", age: 38))
            objectLoaded = true
        }
    }
}

struct BObjectView: View {
    @ObservedObject var bObject: ClassB
    var body: some View {
        VStack(spacing: 30) {
            Text(bObject.name)
            Text("\(bObject.age)")
            Button(action: { self.bObject.age += 1 }) { Text("Increment Age") }
        }
    }
}

(the code is the closest i could come to matching up with your description. if you have other code that doesn't quite fit my example, please consider posting it.)

hope that helps,

DMG

3      

@TC72  

Thank you so much for taking the time to explain all that, I'll give it a try and let you know how I get on!

2      

@TC72  

I really appreciate your solution but I wanted to see if there was something simpler, I'm sure my description overcomplicated what was going on in my code.

In the end both my child views are passed an object from the Array which is of Class B. Something like this:

ViewB( objectB: objectA.arrayOfClassBObjects[0] )

I made sure Class B was also ObservableObject and @Published the variable that's getting set.

I changed the code in View B to this:

@ObservedObject var objectB: ClassB

Doing that fixed the problem I was having immediately.

I'm sure I'll have to learn about Combine later on but I'm happy with this solution. @EnvironmentObject makes it easy for me to access my data from any of my views and when an item I need that's stored by reference I can use @ObservedObject to get updates from it.

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.