I\'m using an NSSlider control, and I\'ve configured it to use continuous mode so that I can continually update an NSTextField with the current value of the slider while the
You could also simply check the type of the current event in the action method:
- (IBAction)sliderChanged:(id)sender
{
NSEvent *currentEvent = [[sender window] currentEvent];
if ([currentEvent type] == NSLeftMouseUp) {
// the slider was let go
}
}
Subclass NSSlider
and implement
- (void)mouseDown:(NSEvent *)theEvent
it's called mouseDown:
, but its called when the know interaction ends
- (void)mouseDown:(NSEvent *)theEvent {
[super mouseDown:theEvent];
NSLog(@"Knob released!");
}
Just found an elegant way to have a slider continuously updating a label, and storing the slider's value only when the user releases all the mouse buttons.
class YourViewController: NSViewController {
@IBOutlet weak var slider: NSSlider!
@IBOutlet weak var label: NSTextField!
@objc var sliderValue = 0
override func awakeFromNib() {
sliderValue = 123 // init the value to whatever you like
slider.bind(NSBindingName("value"), to: self, withKeyPath: "sliderValue")
label.bind(NSBindingName("value"), to: self, withKeyPath: "sliderValue")
}
@IBAction func sliderMoved(_ sender: NSSlider) {
// return if a mouse button is pressed
guard NSEvent.pressedMouseButtons == 0 else { return }
// do something with the value
}
}
Using current application or window event as suggested by other answers might be simpler to some degree, but not bulletproof – tracking can be stopped programmatically + check related comments for other issues. Subclassing both slider and slider cell is by far more reliable and straightforward, however, updating classes in interface builder is a drawback:
// This is Swift 3.
import AppKit
class Slider: NSSlider
{
fileprivate(set) var tracking: Bool = false
}
class SliderCell: NSSliderCell
{
override func startTracking(at startPoint: NSPoint, in controlView: NSView) -> Bool {
(self.controlView as? Slider)?.tracking = true
return super.startTracking(at: startPoint, in: controlView)
}
override func stopTracking(last lastPoint: NSPoint, current stopPoint: NSPoint, in controlView: NSView, mouseIsUp flag: Bool) {
super.stopTracking(last: lastPoint, current: stopPoint, in: controlView, mouseIsUp: flag)
(self.controlView as? Slider)?.tracking = false
}
}
This works for me (and is easier than subclassing NSSlider):
- (IBAction)sizeSliderValueChanged:(id)sender {
NSEvent *event = [[NSApplication sharedApplication] currentEvent];
BOOL startingDrag = event.type == NSLeftMouseDown;
BOOL endingDrag = event.type == NSLeftMouseUp;
BOOL dragging = event.type == NSLeftMouseDragged;
NSAssert(startingDrag || endingDrag || dragging, @"unexpected event type caused slider change: %@", event);
if (startingDrag) {
NSLog(@"slider value started changing");
// do whatever needs to be done when the slider starts changing
}
// do whatever needs to be done for "uncommitted" changes
NSLog(@"slider value: %f", [sender doubleValue]);
if (endingDrag) {
NSLog(@"slider value stopped changing");
// do whatever needs to be done when the slider stops changing
}
}
Took me a little while to find this thread, but the accepted answer (although old) is great for detecting NSSlider state changes (slider value stopped changing being the main one I was looking for)!
Answer in Swift (Swift 4.1):
let slider = NSSlider(value: 1,
minValue: 0,
maxValue: 4,
target: self,
action: #selector(sliderValueChanged(sender:)))
. . .
@objc func sliderValueChanged(sender: Any) {
guard let slider = sender as? NSSlider,
let event = NSApplication.shared.currentEvent else { return }
switch event.type {
case .leftMouseDown, .rightMouseDown:
print("slider value started changing")
case .leftMouseUp, .rightMouseUp:
print("slider value stopped changing: \(slider.doubleValue)")
case .leftMouseDragged, .rightMouseDragged:
print("slider value changed: \(slider.doubleValue)")
default:
break
}
}
Note: the right event types account for someone who has reversed their mouse buttons