swiftnsprogressindicator

Setting IB content filter for NSProgressIndicator color


I have searched here, Google, Apple Documentation, gitHub, etc. and cannot find any information on how to use the Content Filters to set the color of the NSProgressIndicator. I want to do this in macOS.

I have read this post that talks about modifying the NSProgressIndicator programmatically.

However, I set up a Content Filter, namely False Color, on my NSProgressIndicator in IB. It has two colors, Color 1 and Color 2. I set Color 1 to green, and I set color 2 to red.

By setting the Content Filter, False Color, Color 1 to a custom green color, my NSProgressIndicator is now green by default. So, this tells me it is pulling that color from my Content Filter, Color 1, but I do not know how it is doing that.

How would I set the color of the NSProgressIndicator to Content Filter, Color 2, programmatically?

I would post code on how I am approaching this, but I don't even know how to start.

Another post mentioned setting the .appearance, but that lead me to this repository , which doesn't seem to be what I am looking for. I'm willing to do my homework and figure this out, but I'm coming up empty handed using the Content Filters as set in IB.

EDIT 1: After further investigation, if I perform a print(progressBar.contentFilters) I can then see the two colors that were set in the IB's Content Filter, False Colors settings. This is the printout:

[<CIFalseColor: 0x6000026039c0>
inputImage=nil
inputColor0=<CIColor 0x600000cc4570 (0 0.976805 0 1) devicergb>
inputColor1=<CIColor 0x600000cc45d0 (1 0.149131 0 1) devicergb>
]

So now the question is, how do I make the progressBar use the color that is defined as inputColor1?

EDIT 2: So, after messing around with this for a few more hours, I was able to get a halfway working solution, for now. I say halfway because the call to the progress.contentFilters = [Filter Name] causes a complete Window refresh, which looks like a bright single flicker after the first darkGreen filter is applied in the viewDidLoad method (No flickers occur for the initial setting of the filter).

@IBOutlet weak var progressBar: NSProgressIndicator!

// Set the filter properties
let darkGreenFilter = CIFilter(name: "CIFalseColor")!
let redFilter = CIFilter(name: "CIFalseColor")!
// Set the CIColors for the two filters
let darkGreenCIColor = CIColor(red: 0, green: 0.6, blue: 0.4, alpha: 1)
let redCIColor = CIColor(red: 1, green: 0, blue: 0, alpha: 1)
// Set a boolean flag that the progressBar color was changed
var progressBarColorChanged = false

Then in the viewDidLoad:

// Set the values for the filters
darkGreenFilter.setValue(darkGreenCIColor, forKey: "inputColor0")
redFilter.setValue(redCIColor, forKey: "inputColor0")
// Set the progress indicator to dark green
progressBar.contentFilters = [darkGreenFilter]
// Set the progress to zero (removes the colored bar)
progressBar.doubleValue = 0

Then in a later method that monitors the status of the progressBar

if progressBar.doubleValue > 0.75 && !progressBarColorChanged {
    progressBar.contentFilters = [redFilter]
    progressBarColorChanged = true
}

Then once execution is complete I use a reset method to put it all back

progressBar.contentFilter = [darkGreen]
progressBar.doubleValue = 0
progressBarColorChanged = false

I'm slowly getting there. Anyone have any suggestions on how to do this better, or how I can eliminate that pesky screen flicker?


Solution

  • Coming back around to this. Here's my answer

    Swift 5.4 / macOS

    import Cocoa
    
    class ViewController: NSViewController {
    
        let greenFilter = CIFilter(name: "CIFalseColor")!
        let greenCIColor = CIColor(red: 0.3333, green: 0.8667, blue: 0.0, alpha: 1.0)
        let darkGreenCIColor = CIColor(red: 0, green: 0.5373, blue: 0.1137, alpha: 1.0)
        let redFilter = CIFilter(name: "CIFalseColor")!
        let redCIColor = CIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
        let darkRedCIColor = CIColor(red: 0.6275, green: 0.0, blue: 0.0078, alpha: 1.0)
        var redFilterColorChange = false
        var progressBar: NSProgressIndicator?
        var timer = Timer()
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Do any additional setup after loading the view.
            let progressFrame = NSRect(x: view.frame.midX-view.frame.width/2+30, y: view.frame.midY-10, width: view.frame.width-60, height: 20)
            progressBar = NSProgressIndicator(frame: progressFrame)
            self.view.addSubview(progressBar!)
            progressBar?.contentFilters = []
            if NSApp.effectiveAppearance.name == .darkAqua {
                greenFilter.setValue(darkGreenCIColor, forKey: "inputColor0")
                redFilter.setValue(darkRedCIColor, forKey: "inputColor0")
            } else {
                greenFilter.setValue(greenCIColor, forKey: "inputColor0")
                redFilter.setValue(redCIColor, forKey: "inputColor0")
            }
            // Apply the filter
            progressBar?.contentFilters = [greenFilter]
            progressBar?.isHidden = false
            progressBar?.isIndeterminate = false
            progressBar?.doubleValue = 0
            progressBar?.maxValue = 1.0
            startTimer()
        }
    
        override var representedObject: Any? {
            didSet {
            // Update the view, if already loaded.
            }
        }
    
        func startTimer() {
            print("Starting timer")
            timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateProgress), userInfo: nil, repeats: true)
            print("Progress bar frame = \(progressBar!.frame)")
        }
    
        @objc func updateProgress() {
            if progressBar!.doubleValue < 1.0 {
                if progressBar!.doubleValue > 0.75 && !redFilterColorChange {
                    progressBar?.contentFilters = [redFilter]
                    redFilterColorChange = true
                }
                progressBar?.doubleValue += 0.01
                print("Progress bar value = \(progressBar!.doubleValue)")
            } else {
                timer.invalidate()
                progressBar?.contentFilters = [greenFilter]
                progressBar?.doubleValue = 0
                progressBar?.isHidden = true
            }
        }
    }