NSSlider custom subclass - how to maintain the link between the knob position and user interaction?

326 Views Asked by At

Trying to create a custom NSSlider. Overriding the drawKnob() method of the NSSliderCell changes the knob's appearance but doing this somehow disconnects the link between the knob's position and user interactions with the slider.

In the objective C example that is often referenced (https://github.com/lucasderraugh/LADSlider) it looks like when you override drawKnob() you then need to explicitly deal with the startTracking method, but I haven't found a solution that works for me - at the moment I am just setting the cell's startTracking 'at' property to the current value of the slider, but not sure what the right approach is.

Quite a few examples include an NSCoder argument in the cell's custom initialiser - I don't understand why, but maybe this has something to do with ensuring a connection between the display of the knob and the actual slider value?

import Cocoa

class ViewController: NSViewController {

    var seekSlider = NSSlider()
    var seekSliderCell = SeekSliderCell()

    override func viewDidLoad() {
        
        super.viewDidLoad()
        seekSlider = NSSlider(target: self, action: #selector(self.seek(_:)))
        seekSlider.cell = seekSliderCell
        seekSlider.cell?.target = self
        seekSlider.cell?.action = #selector(self.seek(_:))
        seekSlider.isEnabled = true
        seekSlider.isContinuous = true
        view.addSubview(seekSlider)
    }
    
    @objc func seek(_ sender: NSObject) {
        
        let val = seekSlider.cell?.floatValue
        let point = NSPoint(x: Double(val!), y: 0.0)
        seekSlider.cell?.startTracking(at: point, in: self.seekSlider)
    }
}

class SeekSliderCell: NSSliderCell {
    
//  required init(coder aDecoder: NSCoder) {
//      super.init(coder: aDecoder)
//  }
    
    override func drawKnob() {
        
        let frame = NSRect(x: 0.0, y: 6.0, width: 20.0, height: 10.0)
        let c = NSColor(red: 0.9, green: 0.0, blue: 0.6, alpha: 1.0)
        c.setFill()
        NSBezierPath.init(roundedRect: frame, xRadius: 3, yRadius: 3).fill()
    }

    override func startTracking(at startPoint: NSPoint, in controlView: NSView) -> Bool {

       return true
    }
}


1

There are 1 best solutions below

7
On BEST ANSWER

The documentation of drawKnob() states:

Special Considerations If you create a subclass of NSSliderCell, don’t override this method. Override drawKnob(_:) instead.

Instead of

func drawKnob()

override

func drawKnob(_ knobRect: NSRect)

Example:

class ViewController: NSViewController {

    var seekSlider = NSSlider()
    var seekSliderCell = SeekSliderCell()

    override func viewDidLoad() {
        super.viewDidLoad()
        seekSlider = NSSlider(target: self, action: #selector(self.seek(_:)))
        seekSlider.cell = seekSliderCell
        seekSlider.cell?.target = self
        seekSlider.cell?.action = #selector(self.seek(_:))
        seekSlider.isEnabled = true
        seekSlider.isContinuous = true
        view.addSubview(seekSlider)
    }

    @objc func seek(_ sender: NSObject) {
        let val = seekSlider.cell?.floatValue
        print("\(String(describing: val))")
    }

}

class SeekSliderCell: NSSliderCell {

    override func drawKnob(_ knobRect: NSRect) {
        var frame = NSRect(x: 0.0, y: 6.0, width: 20.0, height: 10.0)
        frame.origin.x = knobRect.origin.x + (knobRect.size.width - frame.size.width) / 2
        frame.origin.y = knobRect.origin.y + (knobRect.size.height - frame.size.height) / 2
        let c = NSColor(red: 0.9, green: 0.0, blue: 0.6, alpha: 1.0)
        c.setFill()
        NSBezierPath.init(roundedRect: frame, xRadius: 3, yRadius: 3).fill()
    }

}