Search code examples
swiftredrawnsprogress

Update NSProgress display from within ViewController Function Loop


I'm back again Hive Mind!

In my ViewController class, I have a function which iterates through a series of items in an array. What I would like to happen is to have my progress bar update on each iteration of the loop, but it appears that it doesn't update the progress bar on screen until it completes the entire loop and exits my function.

I'm pretty certain that it's simply because the view only gets redrawn once each time through the application loop (I'm probably not using the right term here.)

What would be the proper way to force the ViewController view to update while I'm still inside of my ViewController function loop?

Here's basically what I'm doing right now:

func myFunction() {
     for item in collection {
         progressBar.increment(by:1)
         progressBar.display()
     }
     // execute my other code
}

When that didn't work, I tried adding a needsSetDisplay after the progressBar.draw method, like this:

func myFunction() {
     for item in collection {
         progressBar.increment(by:1)
         progressBar.display()
         let rect = self.view.visibleRect
         self.view.needsSetDisplay(rect)
     }
     // execute my other code
}

But it still didn't work.

I'm sure there's a way to accomplish what I need to do, but I suspect it involves something a bit more advanced such as running multiple processes, something I'm probably not qualified to do quite yet as I'm new to Swift, and it's been at least 10 years since I've coded in Obj-C (aka I forgot most of what I knew.)

Thanks!

EDIT Another thing I've attempted unsuccessfully was to call progressBar.displayAsNeeded() but it didn't seem to work either.


Solution

  • Assuming you are running on the main thread then you are blocking until the method finishes. No matter which 'need' style methods (setNeedsDisplay, displayAsNeeded, etc) you try to use the actual re-display won't happen until the method has finished (display updated are grouped to avoid it being continually updated).

    What you could do is put the work onto a background thread and then put the UI update back onto the main thread similar to this:

    DispatchQueue.global(qos: .background).async {
        for item in collection {
            DispatchQueue.main.async {
                progressBar.increment(by:1)
                progressBar.display()
            }
        }
    }
    

    Of course that will raise other issues such as the user can now interact with the UI while the background thread is processing which you need to account for.

    If you are already running in a background thread then you just need to dispatch the progress bar update onto the main thread.