Search code examples
objective-ccocoansslider

Hot to create custom NSSlider like "Start screen saver:" slider in System Preferences


How to create custom NSSlider that is working exactly as slider in System Preferences -> Desktop & Screen Saver -> Screen Saver -> Start screen saver: ?

I tried to subclass NSSliderCell with overridden continueTracking: but it don't work as expected.


Solution

  • I played around for a bit and at least got off to a pretty good start with an NSSliderCell subclass.

    MDSliderCell.h:

    #import <Cocoa/Cocoa.h>
    
    @interface MDSliderCell : NSSliderCell {
        BOOL    tracking;
    }
    
    @end
    

    MDSliderCell.m:

    #import "MDSliderCell.h"
    
    @implementation MDSliderCell
    
    - (BOOL)startTrackingAt:(NSPoint)startPoint inView:(NSView *)controlView {
        if ([self numberOfTickMarks] > 0) tracking = YES;
        return [super startTrackingAt:startPoint inView:controlView];
    }
    
    #define MD_SNAPPING 10.0
    
    - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint 
       inView:(NSView *)controlView {
       if (tracking) {
          NSUInteger count = [self numberOfTickMarks];
          for (NSUInteger i = 0; i < count; i++) {
              NSRect tickMarkRect = [self rectOfTickMarkAtIndex:i];
              if (ABS(tickMarkRect.origin.x - currentPoint.x) <= MD_SNAPPING) {
                 [self setAllowsTickMarkValuesOnly:YES];
              } else if (ABS(tickMarkRect.origin.x - currentPoint.x) >= MD_SNAPPING &&
                 ABS(tickMarkRect.origin.x - currentPoint.x) <= MD_SNAPPING *2) {
                 [self setAllowsTickMarkValuesOnly:NO];
              }
          }
       }
      return [super continueTracking:lastPoint at:currentPoint inView:controlView];
    }
    
    - (void)stopTracking:(NSPoint)lastPoint at:(NSPoint)stopPoint
           inView:(NSView *)controlView mouseIsUp:(BOOL)flag {
        [super stopTracking:lastPoint at:stopPoint inView:controlView mouseIsUp:flag];
    }
    
    @end
    

    Basically, during the -continueTracking:at:inView:, it checks to see how close it is to a tick mark, and if it's close enough, it turns on the option to only allow tick mark values. That causes it to snap to the tick mark, then once you get far enough away, you turn the "tick mark-only" option off until you get close enough to another tick mark.