Todd
Todd

Reputation: 1963

SwiftUI not being updated with manual publish

I have a class, a “clock face” with regular updates; it should display an array of metrics that change over time.

Because I’d like the clock to also be displayed in a widget, I’ve found that I had to put the class into a framework (perhaps there’s another way, but I’m too far down the road now). This appears to have caused a problem with SwiftUI and observable objects.

In my View I have:

@ObservedObject var clockFace: myClock

In the clock face I have:

class myClock: ObservableObject, Identifiable {
    var id: Int 
    @Publish public var metric:[metricObject] = []
    ....
    // at some point the array is mutated and the display updates
}

I don’t know if Identifiable is needed but it’s doesn’t make any difference to the outcome. The public is demanded by the compiler, but it’s always been like that anyway.

With these lines I get a runtime error as the app starts:

objc[31175] no class for metaclass

So I took off the @Published and changed to a manual update:

public var metric:[metricObject] = [] {
    didSet {    
        self.objectWillChange.send()`
     }
}

And now I get a display and by setting a breakpoint I can see the send() is being called at regular intervals. But the display won’t update unless I add/remove from the array. I’m guessing the computed variables (which make up the bulk of the metricObject change isn’t being seen by SwiftUI. I’ve subsequently tried adding a “dummy” Int to the myClock class and setting that to a random value to trying to trigger a manual refresh via a send() on it’s didSet with no luck.

So how can I force a periodic redraw of the display?

Upvotes: 1

Views: 595

Answers (2)

Todd
Todd

Reputation: 1963

So it's taken a while but I've finally got it working. The problem seemed to be two-fold.

I had a class defined in my framework which controls the SwiftUI file. This class is sub-classed in both the main app and the widget.

Firstly I couldn't use @Published in the main class within the framework. That seemed to cause the error:

objc[31175] no class for metaclass

So I used @JoshHomman's idea of an iVar that's periodically updated but that didn't quite work for me. With my SwiftUI file, I had:

struct FRMWRKShape: Shape {
  func drawShape(in rect: CGRect) -> Path {
    // draw and return a shape
  }
}

struct ContentView: View {
  @ObservedObject var updater = PeriodicUpdater()
  var body: some View {
    FRMWRKShape()
    //....
    FRMWRKShape() //slightly different parameters are passed in
  }
}

The ContentView was executed every second as I wanted, however the FRMWRKShape code was called but not executed(?!) - except on first starting up - so the view doesn't update. When I changed to something far less D.R.Y. such as:

struct ContentView: View {
 @ObservedObject var updater = PeriodicUpdater()
 var body: some View {
  Path { path in
          // same code as was in FRMWRKShape()
  }
        //....
  Path { path in
     // same code as was in FRMWRKShape()
     // but slightly different parameters
  }
 }
}

Magically, the View was updated as I wanted it to be. I don't know if this is expected behaviour, perhaps someone can say whether I should file a Radar....

Upvotes: 0

Josh Homann
Josh Homann

Reputation: 16327

What is MetricObject and can you make it a struct so you get Equatable for free?

When I do this with an Int it works:

class PeriodicUpdater: ObservableObject {
  @Published var time = 0
  var subscriptions = Set<AnyCancellable>()
  init() {
    Timer
      .publish(every: 1, on: .main, in: .default)
      .autoconnect()
      .sink(receiveValue: { _ in
        self.time = self.time + 1
      })
      .store(in: &subscriptions)
  }
}

struct ContentView: View {
  @ObservedObject var updater = PeriodicUpdater()
  var body: some View {
    Text("\(self.updater.time)")
  }
}

Upvotes: 2

Related Questions