4

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

thisIsTheFoxe
  • 1,584
  • 1
  • 9
  • 30
  • 1
    Did you account for the Beta 5 changes? https://sarunw.com/posts/swiftui-changes-in-xcode-11-beta-5 – matt Jul 31 '19 at 11:22
  • 1
    Yes, I removed all deprecated methods and changed everything to ObservableObjects as you also see in my code.. but I didn't find anything about what changed in computed properties or how the View gets updated.... – thisIsTheFoxe Jul 31 '19 at 11:34
  • Confirming that I'm seeing the same problem. – davidhelms Jul 31 '19 at 23:04
  • I don't see your implementation of `objectWillChange`. – matt Aug 01 '19 at 00:18
  • I'm seeing a similar issue with when I have a subclass of a class conforming to @ObservableObject. [see here](https://stackoverflow.com/q/57615920/4116421) – thiezn Aug 22 '19 at 20:03

3 Answers3

8

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    But maybe I've misunderstood the question? Is it about your `hasPlans`? So you expect it to magically recalculate itself? In that case, yes, you'd need your own publisher and your own subscription; only inside a `body` do you get automatic recalculation when a binding changes. – matt Aug 01 '19 at 02:03
3

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
    }
}
davidhelms
  • 135
  • 4
1

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?

thisIsTheFoxe
  • 1,584
  • 1
  • 9
  • 30