-1

I to create my own custom progressbar ie:NSProgressIndicator using swift how do I do that?

Death Guard
  • 380
  • 3
  • 15

2 Answers2

3

You can just make your own progress indicator, e.g.:

@IBDesignable
open class ColorfulProgressIndicator: NSView {
    @IBInspectable open var doubleValue: Double = 50 { didSet { needsLayout = true } }
    @IBInspectable open var minValue: Double = 0     { didSet { needsLayout = true } }
    @IBInspectable open var maxValue: Double = 100   { didSet { needsLayout = true } }

    @IBInspectable open var backgroundColor: NSColor = .lightGray { didSet { layer?.backgroundColor = backgroundColor.cgColor } }
    @IBInspectable open var progressColor:   NSColor = .blue      { didSet { progressShapeLayer.fillColor = progressColor.cgColor } }
    @IBInspectable open var borderColor:     NSColor = .clear     { didSet { layer?.borderColor = borderColor.cgColor } }
    @IBInspectable open var borderWidth:     CGFloat = 0          { didSet { layer?.borderWidth = borderWidth } }
    @IBInspectable open var cornerRadius:    CGFloat = 0          { didSet { layer?.cornerRadius = cornerRadius } }

    private lazy var progressShapeLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = progressColor.cgColor
        return shapeLayer
    }()

    public override init(frame: NSRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }

    // needed because IB doesn't don't honor `wantsLayer`
    open override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        layer = CALayer()
        configure()
    }

    open override func layout() {
        super.layout()
        updateProgress()
    }

    open func animate(to doubleValue: Double? = nil, minValue: Double? = nil, maxValue: Double? = nil, duration: TimeInterval = 0.25) {
        let currentPath = progressShapeLayer.presentation()?.path ?? progressShapeLayer.path

        // stop prior animation, if any
        progressShapeLayer.removeAnimation(forKey: "updatePath")

        // update progress properties
        if let doubleValue = doubleValue { self.doubleValue = doubleValue }
        if let minValue = minValue       { self.minValue = minValue }
        if let maxValue = maxValue       { self.maxValue = maxValue }

        // create new animation
        let animation = CABasicAnimation(keyPath: "path")
        animation.duration = duration
        animation.fromValue = currentPath
        animation.toValue = progressPath
        progressShapeLayer.add(animation, forKey: "updatePath")
    }
}

private extension ColorfulProgressIndicator {
    func configure() {
        wantsLayer = true

        layer?.cornerRadius = cornerRadius
        layer?.backgroundColor = backgroundColor.cgColor
        layer?.borderWidth = borderWidth
        layer?.borderColor = borderColor.cgColor

        layer?.addSublayer(progressShapeLayer)
    }

    func updateProgress() {
        progressShapeLayer.path = progressPath
    }

    var progressPath: CGPath? {
        guard minValue != maxValue else { return nil }
        let percent = max(0, min(1, CGFloat((doubleValue - minValue) / (maxValue - minValue))))
        let rect = NSRect(origin: bounds.origin, size: CGSize(width: bounds.width * percent, height: bounds.height))
        return CGPath(rect: rect, transform: nil)
    }
}

You can either just set its doubleValue, minValue and maxValue, or if you want to animate the change, just:

progressIndicator.animate(to: 75)

For example, below I set the progressColor and borderColor to .red, set the borderWidth to 1, set the cornerRadius to 10. I then started animating to 75, and then, before it’s even done, triggered another animation to 100 (to illustrate that animations can pick up from wherever it left off):

enter image description here

There are tons of ways of implementing this (so get too lost in the details of the implementation, above), but it illustrates that creating our own progress indicators is pretty easy.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • ok yes this is exactly what i was looking for thank you so much. I'm new to this so i wasn't sure what terms to use. – Death Guard Feb 06 '20 at 05:54
0

You can subclass it just like any other view. But for all you're doing that is likely unnecessary.

class CustomIndicator: NSProgressIndicator {
    // ...
}

As far as setting the height goes, you can do this by initializing the view with a custom frame.

let indicator = NSProgressIndicator(frame: NSRect(x: 0, y: 0, width: 100, height: 20))

There is a property called controlTint that you can set on NSProgressIndicator, but this only allows you to set the color to the predefined ones under NSControlTint. For truly custom colors, I'd recommend this route using Quartz filters via the Interface Builder.