Search code examples
macosswift3nsprogressindicatornsprogress

How to update the NSProgressIndicator?


I have problem with NSProgress. The problem is that NSProgressIndicator is not updating during the process and showing only a small completed portion at the end of process. The localizedDescription is also showing only at the end of the process, but as 100% completed.

So, I have a class with one method findRepeatsWithProgressReporting using NSProgress

class TestProgress: NSObject, ProgressReporting
{
    let progress: Progress

    override init()
    {
        progress = Progress()
        super.init()
    }

    func findRepeatsWithProgressReporting(stringToSearch: String, minimalLength: Int, maximalLength: Int) -> [String]
    {
        var arrayOfRepeats = [String]()

        progress.totalUnitCount = Int64((minimalLength...maximalLength).count)

        for i in minimalLength...maximalLength
        {
            let arrayOfStrings = stringToSearch.chopString(stringOut: stringToSearch, length: i)
            let arrayOfUniqueStrings = Array(Set(arrayOfStrings))

            for each in arrayOfUniqueStrings
            {
                let arrayOfNSRanges = stringToSearch.searchForNSRangesOfStringInString(stringOut: stringToSearch, stringIn: each)

                var positions = String()

                if arrayOfNSRanges.count > 1
                {
                    for each1 in arrayOfNSRanges
                    {
                        let repeatStart = String(each1.location + 1)
                        let repeatEnd = String(each1.location + each1.length)
                        positions += "(" + repeatStart + "-" +  repeatEnd + ")"
                    }
                    let stringToShow = each + " " + positions
                    arrayOfRepeats.append(stringToShow)
                }
            }
            progress.completedUnitCount += 1
        }
        return  arrayOfRepeats
    }

}

Then, in myVewContrloler I have parentProgress repeatsProgress having totalUnitCount: 10 and have added the task of the method findRepeatsWithProgressReporting as childProgress to the parentProgress repeatsProgress using repeatsProgress.becomeCurrent(withPendingUnitCount: 10).

private var progressObservationContext = 0

class myVewContrloler: NSViewController
{
    ...

    var testProgress = TestProgress ()
    var repeatsProgress = Progress() 

    @IBOutlet weak var repeatsSearchProgressBar: NSProgressIndicator!
    @IBOutlet weak var repeatsPercentText: NSTextField!

    @IBOutlet weak var minimalLength: NSTextField!
    @IBOutlet weak var maximalLength: NSTextField!
    @IBOutlet var foundRepeats: NSTextView!

    @IBAction func actionFindRepeats(_ sender: AnyObject)
    {
        repeatsProgress = Progress(totalUnitCount: 10)

        let options : NSKeyValueObservingOptions = [.new, .old, .initial, .prior]
        repeatsProgress.addObserver(self, forKeyPath: "fractionCompleted", options: options, context: &progressObservationContext)
        repeatsProgress.addObserver(self, forKeyPath: "localizedDescription", options: options, context: &progressObservationContext)

        var arrayOfRepeats = [String]()

        repeatsProgress.becomeCurrent(withPendingUnitCount: 10)
        arrayOfRepeats = testProgress.findRepeatsWithProgressReporting(stringToSearch: stringToSearch, minimalLength: minimalLength.integerValue, maximalLength: maximalLength.integerValue)

        ...

        repeatsProgress.removeObserver(self, forKeyPath: "fractionCompleted")
        repeatsProgress.removeObserver(self, forKeyPath: "localizedDescription")

        repeatsProgress.resignCurrent()
    }
}

The last part is for KVO :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
        guard context == &progressObservationContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    if keyPath == "fractionCompleted"
    {
        OperationQueue.main.addOperation{
                let progress = object as! Progress
                self.repeatsSearchProgressBar.doubleValue = progress.fractionCompleted
                self.repeatsPercentText.stringValue = progress.localizedDescription
        }
    }
}

I have added

print("Observed Something")

inside of the

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{  ...

and what I see is two times printing the "Observed Something"immediately after start and six times at the end, no printing in between (as it expected to be for the updating process). What can be the reason ?


Solution

  • This seems like a concurrency problem. Since func actionFindRepeats(_ sender: AnyObject) is running in the main thread, it's concurring with the UI updates, which affects the NSProgressIndicator directly.

    See the last example of that answer for more details about that:

    You can try adding all the content of your actionFindRepeats function into that block and see if it works:

    DispatchQueue.global().async {
        // qos' default value is ´DispatchQoS.QoSClass.default`
    }
    

    Reference for that block: