-1

I'm starting coding in Swift, but trying to make an app well, using best practices.

Application should control Philips Hue bridge to change lights states in home. It reads and send http requests from the Hue bridge and must sync it with visible controllers in my app.

Objects received from Hue bridge are stored in HueSession() class and its subclasses and are mapped initially to array of ViewModels and shown on screen. Everything works well up to this point.

Then I want to subscribe to these ViewModels and receive event when user changes application controls. This would allow me to send http request back to Hue bridge.

Unfortunately, although the label on Toggle changes properly, I'm receiving just one event per light when application starts, like this...

false set on Lightstrip 1
false set on Hue color lamp 2
false set on Hue color lamp 1
false set on Hue lightstrip plus

Changing Toggle state in my app does not print the message, but just changes Toggle label text: ON or OFF. Am I using sink in a wrong way? Or appending class to array makes a copy of it, instead a reference?

ApplicationData.swift

class ApplicationData: ObservableObject {
@Published var hueResources: HueSession
@Published var bulbs: [BulbViewModel]

    var hue = HueController()
    
    
    init(){
        bulbs = []
        hueResources = HueSession()
        
        hue.GetLightsList() {
            resources in
            if resources != nil {
                self.hueResources = resources!
                
                self.hueResources.data.map(){
                    value in
                    
                    let bulb = BulbViewModel(id: value.id)
                    bulb.name = value.metadata.name
                    bulb.isOn = value.on.on
                    
                    bulb.$isOn.sink { value in print("\(value) set on \(bulb.name)") }
                    
                    self.bulbs.append(bulb)
                }
                
            }
        }
    }

}

BulbViewModel.swift

class BulbViewModel: ObservableObject, Identifiable {
    @Published
    var color = 250.0
    
    @Published
    var amplitude = 250.0
    
    @Published
    var isOn = false
    
    var isOnText: String {
        get {
            isOn ? "ON" : "OFF"
        }
    }
    
    @Published
    var name: String = ""
    
    @Published
    var id: String
    
    init(id: String){
        self.id = id
    }
}

BulbView.swift

struct BulbView: View {
    @ObservedObject var bulbViewModel: BulbViewModel

    var body: some View {
        VStack{
            Text("Light: \(bulbViewModel.name)")
            Slider(value: $bulbViewModel.amplitude, in: 1...254, step: 1.0)
            Slider(value: $bulbViewModel.color, in: 153...500, step: 1.0)
            Toggle("\(bulbViewModel.isOnText)", isOn: $bulbViewModel.isOn)
        }
    }
}

First I tried subscribing to on change methods on the View components, but it does not sound like a good practice. I don't want to receive tasks in UI layer.

Kamil Sko
  • 1
  • 4
  • You can't chain observable objects, with SwiftUI you should start with a `struct` and only if needed switch to a `class`. You are taking the long complicated approach. – lorem ipsum Feb 04 '23 at 17:03
  • Thank you @loremipsum for the reply. I'm not sure if I understand well the idea: "You can't chain observable object". Where am I doing that? Thanks for the tip about struct. I will double check how to apply it in this case. – Kamil Sko Feb 05 '23 at 21:36
  • Your bulbs variable, those can and should be structs – lorem ipsum Feb 05 '23 at 21:50

1 Answers1

-1

Changed ViewModel to struct and instead of subscribing to publisher, using now simply didSet for properties that should trigger event.

struct BulbViewModel: Identifiable {

var color: Double = 250.0 {
    didSet {
        print("\(oldValue) => \(color)")
    }
}

...

Thanks @lorem ipsum for advice!

Kamil Sko
  • 1
  • 4