Swift: How to use NSProgressIndicator?

PjotrC picture PjotrC · Jul 5, 2016 · Viewed 10.6k times · Source

I want to use an NSProgressIndicator to show some progress. But I couldn't find a method which increases the bar of that control. I did also not find an example on the web.

let progressbar = NSProgressIndicator()
progressbar.frame = NSRect(x: 100, y: 20, width: 150, height: 10)
progressbar.minValue = 0
progressbar.maxValue = 10
self.window.contentView?.addSubview(progressbar)

for i in 0...10 {
    progressbar.incrementBy(1)  //has no effect
}

Answer

NSGod picture NSGod · Jul 6, 2016

You won't be able to demonstrate a progress bar in such a tight loop like that.

When you set the value of the progress indicator, the OS X display mechanism won't actually draw the difference until the next time through the event loop, which doesn't happen until after your method returns. In other words, you're setting it all the way to 10 before the progress indicator even gets a chance to redraw itself, so all you see is the final filled state.

You could theoretically force the progress indicator to display after each time through the loop (progressbar.display()), but I don't think you'd be able to distinguish the differences when they happen within, say, 0.01 seconds.

The solution then, is to introduce a small delay between calling incrementBy(1), so that the next event loop can happen and the display of the new value will take place. You can do that by adding something like the following to your code:

func delay(delay:Double, closure:()->()) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
        Int64(delay * Double(NSEC_PER_SEC))),
                   dispatch_get_main_queue(), closure)
}

class AppDelegate: NSObject, NSApplicationDelegate {
    @IBOutlet weak var progressIndicator: NSProgressIndicator!

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        let progressbar = NSProgressIndicator()
        progressbar.frame = NSRect(x:100, y:20, width:150, height:10)
        progressbar.minValue = 0
        progressbar.maxValue = 10
        self.window.contentView?.addSubview(progressbar)

        self.progressIndicator = progressbar
        progressIndicator.indeterminate = false

        for i in 0...10 {
            delay(1.0 * Double(i), closure: { 
                self.progressIndicator.incrementBy(1)
            })
        }
    }
}

This queues up the calling of incrementBy(1) to be at 1 second intervals.