0

I decode json data from an iPhone to a watch and store it into an object (custom class: PeopleObj) which is declared as @ObservableObject. However, the contenview containing this object doesn't get the data

This is the data model:

struct Person: Codable {
    var pid : UUID
    var dept: String
    var name: String

    init(pid: UUID, dept: String, name: String){
        self.pid = pid
        self.dept = dept
        self.name = name
    }
}
    
class PeopleObj: ObservableObject, Identifiable , Codable{
    @Published var people: [Person] = []
        
    // get codable
    init() { }
    
    enum CodingKeys: CodingKey {
        case people
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(people, forKey: .people)
        
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        people = try container.decode(Array.self, forKey: .people)
    }
}

Following is the decode part at WatchOS, here I get the json data as a reply on a request and decode it into the peopleObj:

let jsonEncoder = JSONEncoder()
do {
    let jsonDataRequest = try jsonEncoder.encode(request)
    print("send request")
    self.session?.sendMessageData(jsonDataRequest, replyHandler: { response in
        print(">>>>>>>>>>>>>>>>> DATA-Reply vom iPhone received: \(response)")
        DispatchQueue.main.async {
            let jsonDecoder = JSONDecoder()
            do {
                self.peopleObj = try jsonDecoder.decode(PeopleObj.self, from: response)
                for person in self.peopleObj.people {
                    print("received person: \(person.name), dept \(person.dept)")
                    // here it prints correctly
                }
                                
            } // End do decode
            catch { print("decode catch!!!!!!!")  }
        } // Dispatch main
    },
    errorHandler: { error in
        print("Error sending message: %@", error)
    }) // sendMessageData
} // End do decode
catch { print("encode catch!!!!!!!")  }

This is the contenview, observing the object:

struct ContentView: View {
        
    @ObservedObject var peopleObj: PeopleObj
        
    var body: some View {
     
        VStack {
            List(peopleObj.people, id: \.pid) { person in
                HStack {
                    Text("name: \(person.name)")
                    Spacer()
                    Text("dept: \(person.dept)")
                }
            }
        }
    }
}

I tried decoding the json into a struct and then manualy add the values to the peopleObj - that works but doesn't seem to be the correct process to me! I can't explain why, guess it has something to do with value vs referencing.

Any help and or idea is more than welcome!!!!!

Edit: The peopleObj is defined in the HostingController:

    class HostingController: WKHostingController<ContentView> , WCSessionDelegate{
   
   @ObservedObject var peopleObj:PeopleObj = PeopleObj()

The ContentView is called from the HostingController

 override var body: ContentView {
     return ContentView(peopleObj: peopleObj)
}
Red
  • 1,425
  • 1
  • 10
  • 15
  • When and how do you inject your PeopleObj into your View? – Joakim Danielson Aug 11 '20 at 11:24
  • Thank you for taking your time!!!! The call is from the hosting controller: override var body: ContentView { return ContentView(peopleObj: peopleObj) } It is called before the reply from the iPhone is received, however, because of the Obeserved I expect the update. The object is defined in the hosting controller as well as observed: @ObservedObject var peopleObj:PeopleObj = PeopleObj() – Red Aug 11 '20 at 11:30
  • It's very hard to read code posted in comments but what I am suspicious about is that you do `PeopleObj()` in your hosting controller but the json decoding also creates a PeopleObj object, are you sure your view holds the correct PeopleObj instance? – Joakim Danielson Aug 11 '20 at 11:58
  • To make myself clear, don't post more code in the comments. Update your question instead since it is easier to read and also it will hopefully make the question clearer for any other readers. – Joakim Danielson Aug 11 '20 at 11:59
  • Thanks, will do. The idea, that I create a new instance of PeopleObj sound reasonable. But somehow I'm lost now, how else could I code it. I need to define the instance before I get the reply from the iPhone and actually want to update it with the reply - that why I thought referring to self.peopleObj wouldd be the best way. – Red Aug 11 '20 at 12:03
  • Maybe it shouldn't be the same type, maybe you should have some wrapper object that is observable and holds a published property that is your PeopleObj. Remove ObservableObject from PeopleObj and create `PeopleObjWrapper: ObservableObject` – Joakim Danielson Aug 11 '20 at 12:26
  • You can pass the initial instant of `PeopleObj` as an `@EnvironmentObject` or use a Singleton Pattern https://developer.apple.com/documentation/swift/cocoa_design_patterns/managing_a_shared_resource_using_a_singleton. Every view that needs the array would just access PeopleObj,sharedInstance – lorem ipsum Aug 11 '20 at 12:35
  • Thanks to both of you, solved it with the hint from Joakim - do you want to post it as an answer so I can accept it? The Problem with the EnvironmentObject is, that I have no idea, how to inject the EnvironmentObject already in the hostingcontroller, as I need the hosting controller as the WCsession delegate, do you happen to know how this would be possible? – Red Aug 11 '20 at 13:27

0 Answers0