Search code examples
swiftcore-dataswiftuicomputed-propertiescombine

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


So, I want to have a Text that changes its content based on the contents of my CoreData Model. To do that I used a computed property in Xcode beta 4 but it doesn't seem to work anymore. Either that's a bug or there is some other issue I don't see?

The problem I have exactly is that my View (and the computed property) don't seem to get updated when self.objectWillChange.send() is called in my store.

I also tried to 'export' my var into the store and get it from there, with the same result...


EDIT: I just tried the same with another class and it didn't work with just objectWillChange.send() but only with @Published however, even that stopped working if the class inherited from NSObject...


I just found out: with

struct Today: View {
    @EnvironmentObject var myStore: DateStore
    var hasPlans: Bool {
        guard let plans = myStore.getPlans() else { return false }
        return plans.isEmpty
    }

    var body: some View{
        VStack{
            Text(hasPlans ? "I have plans":"I have time today")
            Button(action: {
                self.myStore.addPlans(for: Date())
            }) {
                Text("I have plans")
            }
    }
}

class DateStore: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
    private var fetchedResultsController: NSFetchedResultsController<DateStore>
    //...
    public func addPlans(for date: Date){
        //{...}
        if let day = self.dates.first(where: { $0.date == date}){
            day.plans += 1
            saveChanges()
        }else{
            self.create(date: dayDate)
        }
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        self.objectWillChange.send()
    }
}


That's a very simplified version of my problem and I know that my DataModel works because the values change and self.objectWillChange.send() is called, but my View isn't updated for some reason....


Solution

  • 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.