Computed (NSObject) Properties in SwiftUI don't update the view

只谈情不闲聊 提交于 2019-12-06 09:24:39

I don't see that NSObject is the source of the problem. The problem seems to be that you haven't implemented objectWillChange. The compiler will let you get away with that, but the result is that your objectWillChange does nothing.

Here's a simple example that shows how to configure an ObservableObject (that is an NSObject) with a computed property whose binding works:

class Thing : NSObject, ObservableObject {
    let objectWillChange = ObservableObjectPublisher()
    var computedProperty : Bool = true {
        willSet {
            self.objectWillChange.send()
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var thing : Thing
    var body: some View {
        VStack {
            Button(self.thing.computedProperty ? "Hello" : "Goodbye") {
                self.thing.computedProperty.toggle()
            }
            Toggle.init("", isOn: self.$thing.computedProperty).frame(width:0)
        }
    }
}

You can see by tapping the button and the switch that everything is live and responsive to the binding within the view.

Experimenting with my own code exhibiting similar problem.

It looks like the @Publisher SwiftUI magic breaks when the Class adopting ObservableObject is a subclass of NSObject.

The short answer solution is that if you can get it working with @Published when it is not a subclass of NSObject, then when you make it a subclass of NSObject, replace @Published with

let objectWillChange = PassthroughSubject<Void, Never>()

You'll have to import the Combine framework in your Class file to do this.

Here is some code from the working app I'm experimenting with.

The View:

import SwiftUI

struct ContentView: View {

    //Bind using @ObservedObject
    @ObservedObject var provider: Provider

    var body: some View {
        Text("\(self.provider.distance)")
    }
    ...
}

The Class:

import Combine

class Provider: NSObject, ObservableObject {

    //Instead of using @Published, use objectwillchange
    //@Published var distance: CLLocationDistance = 0.0
    let objectWillChange = PassthroughSubject<Void, Never>()
    var distance = 0.0
    ...

    func calculateDistance() {
        ...
        // publish the change
        self.objectWillChange.send()
        self.distance = newDistance
    }
}

One solution that is working is to simply make a new @State var instead of using the computed property. However, according WWDC talks about SwiftUI this feels somewhat wrong because my 'actual state' is living in my data model and by declaring a new @State I need to keep both of them in sync which is against the "Single Source of Truth" pattern isn't it?

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!