0

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
    }
}


chemFour
  • 140
  • 7
  • Any clues in the documentation of `drawKnob`? – Willeke Mar 03 '22 at 12:54
  • The documentation does mention the need to send a 'lockFocus()' message but if I call lockFocus() on the NSSlider I get a warning that it is deprecated and nothing has changed in the compiled app. There may be something around this I am missing I guess – chemFour Mar 03 '22 at 12:59

1 Answers1

1

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()
    }

}
Willeke
  • 14,578
  • 4
  • 19
  • 47
  • When I tried to 'hard code' an argument to the drawKnob method I received a "Method does not override any method from its superclass"... and when I tried to call drawKnob to pass in an actual NSRect argument I get 'Value of type 'NSCell?' has no member 'drawKnob'... I wonder if it is actually correct to link the control (slider) and its cell through 'seekSlider.cell = seekSliderCell' ...but couldn't see another way to do it – chemFour Mar 03 '22 at 15:07
  • ...or I can literally just declare 'override func drawKnob(_ knobRect: NSRect) { ....' which does result in a knobRect property but doesn't fix the issue that the position of the knob is disconnected from user interactions with the slider – chemFour Mar 03 '22 at 15:23
  • 1
    @chemFour I added an example. – Willeke Mar 03 '22 at 16:04
  • Thank you very much for your example. I had understood that '(_ knobRect: NSRect)' meant - pass in an NSRect but don't bother labelling this argument. Can I ask, is there anything about the drawKnob(_:) documentation/ specification that communicates to the programmer that the 'knobRect: NSRect' argument actually means that no argument needs to be passed in? – chemFour Mar 04 '22 at 10:26
  • `func drawKnob(_ knobRect: NSRect)` has one argument and it's not optional. [drawKnob()](https://developer.apple.com/documentation/appkit/nsslidercell/1444606-drawknob/) and [drawKnob(_:)](https://developer.apple.com/documentation/appkit/nsslidercell/1444600-drawknob/) are two methods with almost the same name. – Willeke Mar 04 '22 at 11:26
  • My question is about the fact that the NSRect property labelled 'knobRect' is never initialised (or is initialised internally automatically I guess). My understanding of ```func drawKnob(_ knobRect:NSRect)``` is that an initialised NSRect needs to be passed in. But... here no NSRect is ever initialised for knobRect. What about this ```drawKnob(_:)``` method makes it clear that ```knobRect: NSRect``` describes a property that is not actually initialised by the programmer? – chemFour Mar 04 '22 at 11:39
  • It's in the syntax. `func functionName(_ paramA: Int, paramB: Int) {}` the part between `()` is the parameters. See [Functions](https://docs.swift.org/swift-book/LanguageGuide/Functions.html). The slider intializes a `NSRect` and passes it to `drawKnob(_:)`. – Willeke Mar 04 '22 at 13:02