0

I have a SwiftUI view MainExportMenu which is controlled by an Export controller in an event driven app. The UI triggers the Export controller to create a document, which is done on another thread. Once the document is ready, an event is triggered that calls receive in the app.

When this happens, I want to update the UI by toggling isReadyForExport to cause a Spinner to show.

If I do this directly from the function that the button calls, this works as intended, however if this is done from the event, this results in the UI not being updated. How would an event call update the UI?

Below is my code:

struct MainExportMenu: View {
    @StateObject var exportController = ExportController()

    var body: some View {
        Button(action: {
            exportController.generate(IDs: self.IDs, as: self.exportType)
        })

        if exportController.isReadyForExport {
            Spinner()
        }
    }
}

And controller

class ExportController: ObservableObject {
    init() {
        app.add(self) //This adds it to the app for the event framework to reference back when it publishes its event. This is retained
    }
    
    deinit {
        app.remove(self) // removes it's reference from the event framework
    }
    
    @Published var isReadyForExport = false {
        didSet {
            self.objectWillChange.send()
        }
    }
    
    //This function gets an event from anthother part of the App - this does call successfully but does not update the UI
    func receive(_ event: Events.Event) -> Bool {
        print("I am here and recieved data") //this is called
        self.isReadyForExport = true //does not update the UI but objectWIllChagne.send() is called
        return true
    }

    func generate(IDs exportIds: [UUID], as exportType: ExportType?) {
        //self.isReadyForExport = true - THIS WILL WORK - if the call is made here it will work
        
        prepareDocuments(exportIds: exportIds, exportType: exportType) // calls an event that another thread will receive which will result in receive being called
    }
}

I have tested the following approaches with no success:

  • ensured that the ExportController is the same object via a memory address when called from event and UI
  • ensured that the ExportController is on the main thread
  • Attempted to use DispatchQueue.main.async { self.isReadyForExport = true } as suggested here: Update a variable that changes the UI from background thread - SWIFTUI
  • Using ObservedObject instead of StateObject
Rob
  • 415,655
  • 72
  • 787
  • 1,044
Osian
  • 171
  • 1
  • 14
  • 1
    `@ObservedObject var exportController = ExportController()` should be `@StateObject var exportController = ExportController()`. When initially creating an observed class, use `StateObject`. – Yrb Jun 15 '23 at 16:32
  • Making it to a @StateObject var exportController = ExportController() made no difference and did not result in the UI being updated – Osian Jun 15 '23 at 16:37
  • So, you are saying the issue is with `func receive` that is called from another part of the app, but you don't show that code. This really needs a [Minimal, Reproducible Example (MRE)](https://stackoverflow.com/help/minimal-reproducible-example). – Yrb Jun 15 '23 at 16:46
  • The receive event is called from an internal event framework. But the event is called from a main thread after completing an async task. – Osian Jun 15 '23 at 17:09
  • How is it called if it doesn't have a reference to that instance of `ExportController`? Agree that it needs a [mre] – jnpdx Jun 15 '23 at 17:51
  • I've added a bit more context to the code - when we create the controller, it adds itself to the event controller, which then removes itself when it's done – Osian Jun 15 '23 at 17:58
  • make sure `self.isReadyForExport = true` happens on main thread. – timbre timbre Jun 15 '23 at 18:28
  • I took your code, got it to compilable state (without changing the logic, just by dropping params), and it works. So I think you didn't share with us the cause of the problem, not possible to answer. – timbre timbre Jun 15 '23 at 18:42
  • Just reiterating what others have said, the problem does appear to rest in the above code (though, I’d lose that redundant `objectWillChange.send` … that’s the whole idea of `@Published`, [namely that](https://developer.apple.com/documentation/combine/observableobject) “By default an `ObservableObject` synthesizes an `objectWillChange` publisher that emits the changed value before any of its `@Published` properties changes” … it’s not the source of the problem, but it’s weird to `objectWillChange` when `@Published` does that for you). – Rob Jun 16 '23 at 12:53

0 Answers0