Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to trigger onChange on an ObservableObject?

Tags:

swift

swiftui

Let's suppose a simple ObservableObjectview model with a few published properties.

class ViewModel: ObservableObject, Equatable {
    static func == (lhs: ViewModel, rhs: ViewModel) -> Bool {
        lhs.value == rhs.value &&
        lhs.anotherProperty1 == rhs.anotherProperty1 &&
        lhs.anotherProperty2 == rhs.anotherProperty2 &&
        lhs.anotherProperty3 == rhs.anotherProperty3
    }
    
    @Published var value = 0
    @Published var anotherProperty1 = "foo"
    @Published var anotherProperty2 = "bar"
    @Published var anotherProperty3 = "baz"
}

I would like onChange to be triggered no matter which ViewModel property is modified.

struct DemoView: View {
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        Button {
            viewModel.value += 1
        } label: {
            Text("Increment value \(viewModel.value.formatted())")
        }
        .onChange(of: viewModel) { _ in debugPrint("viewModel did change") } // FIXME: does not trigger :(
    }
}

Here I change value for example. Why is onChange never triggered?

like image 600
parapote Avatar asked Oct 25 '25 14:10

parapote


2 Answers

One workaround is to create an additional variable that changes whenever any of the other ones changes.

For example, variable status in the example below will have a different value any time one of the other variables changes. Then, you monitor the changes only for the variable status.

class ViewModel: ObservableObject, Equatable {
    static func == (lhs: ViewModel, rhs: ViewModel) -> Bool {
        lhs.value == rhs.value &&
        lhs.anotherProperty1 == rhs.anotherProperty1 &&
        lhs.anotherProperty2 == rhs.anotherProperty2 &&
        lhs.anotherProperty3 == rhs.anotherProperty3
    }
    
    @Published private(set) var status = UUID()
    
    @Published var value = 0 { didSet { status = UUID() } }
    @Published var anotherProperty1 = "foo" { didSet { status = UUID() } }
    @Published var anotherProperty2 = "bar" { didSet { status = UUID() } }
    @Published var anotherProperty3 = "baz" { didSet { status = UUID() } }
}

In the view:

struct DemoView: View {
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        Button {
            viewModel.value += 1
        } label: {
            Text("Increment value \(viewModel.value.formatted())")
        }
        .onChange(of: viewModel.status) { _ in debugPrint("viewModel did change") }
    }
}
like image 67
HunterLion Avatar answered Oct 27 '25 06:10

HunterLion


Why is onChange never triggered?

Because it's a class (reference type) and the reference of the class (unlike a struct) does not change on any modification of a property.

But maybe you don't understand the functionality if @Published. You don't need onChange at all because objectWillChange will be called automatically whenever one of the @Published properties changes. That's the main goal of ObservableObject.

like image 21
vadian Avatar answered Oct 27 '25 05:10

vadian