6

How can I create NSSlider animation when changing float value of it. I was trying:

[[mySlider animator] setFloatValue:-5];

but that didn't work.. just change the value without animation. So maybe someone knows how to do this?

Thanks in advance.

3 Answers3

6

Ok - so this isn't as quick and pretty as I hoped but it works. You can't actually use animators and Core Animation on the slider knob - because Core Animation works only on layers and there's no access to the knob values in the slider layer.

So we have to resort instead to manually animating slider value. Since we're doing this on a Mac - you can use NSAnimation (which isn't available on iOS).

What NSAnimation does is simple - it provide an timing/interpolation mechanism to allow YOU to animate (as opposed to Core Animation which also connects to the views and handles the changes to them).

To use NSAnimation - you most commonly would subclass it and override setCurrentProgress: and put your logic in there.

Here's how I implemented this - I created a new NSAnimation subclass called NSAnimationForSlider

NSAnimationForSlider.h :

@interface NSAnimationForSlider : NSAnimation  
{  
    NSSlider *delegateSlider;  
    float animateToValue;    
    double max;   
    double min;  
    float initValue;  
}  
@property (nonatomic, retain) NSSlider *delegateSlider;  
@property (nonatomic, assign) float animateToValue;    
@end  

NSAnimationForSlider.m :

#import "NSAnimationForSlider.h"

@implementation NSAnimationForSlider
@synthesize delegateSlider;
@synthesize animateToValue;

-(void)dealloc
{
    [delegateSlider release], delegateSlider = nil;
}

-(void)startAnimation
{
    //Setup initial values for every animation
    initValue = [delegateSlider floatValue];
    if (animateToValue >= initValue) {
        min = initValue;
        max = animateToValue;
    } else  {
        min = animateToValue;
        max = initValue;
    }

    [super startAnimation];
}


- (void)setCurrentProgress:(NSAnimationProgress)progress
{
    [super setCurrentProgress:progress];

    double newValue;
    if (animateToValue >= initValue) {
        newValue = min + (max - min) * progress;        
    } else  {
        newValue = max - (max - min) * progress;
    }

    [delegateSlider setDoubleValue:newValue];
}

@end

To use it - you simply create a new NSAnimationForSlider, give it the slider you are working on as a delegate and before each animation you set it's animateToValue and then just start the animation.

For example:

slider = [[NSSlider alloc] initWithFrame:NSMakeRect(50, 150, 400, 25)];
[slider setMaxValue:200];
[slider setMinValue:50];
[slider setDoubleValue:50];

[[window contentView] addSubview:slider];

NSAnimationForSlider *sliderAnimation = [[NSAnimationForSlider alloc] initWithDuration:2.0 animationCurve:NSAnimationEaseIn];
[sliderAnimation setAnimationBlockingMode:NSAnimationNonblocking];
[sliderAnimation setDelegateSlider:slider];
[sliderAnimation setAnimateToValue:150];

[sliderAnimation startAnimation];
shein
  • 1,834
  • 15
  • 23
  • Thanks for the answer, maybe you can say more accurate how to do this? because I didn't understand exactly what do you mean. What do you mean saying basic animation? "animator" I think is basic animation. –  Jan 27 '12 at 08:43
  • I'll be at my desktop soon and try to write it up in code for you – shein Jan 27 '12 at 09:42
  • Right, we are talking about Mac. –  Jan 27 '12 at 17:21
  • wow! It's not so easy! THANK YOU VERY VERY VERY MUCH for your help!!! I will go to test and use Your code! Wow again, Thank You very much!!! –  Jan 27 '12 at 20:06
  • 1
    Pleasure is mine - if you find problems or improvements - post 'em back here. Good luck! – shein Jan 27 '12 at 20:26
  • Don't be confused by sliderDelegate. That is NOT the actual delegate and will not receive NSAnimationDelegate callbacks, you still want to set some controller or your app delegate to the delegate and implement NSAnimationDelegate methods there to get callbacks or handle animation progress or completion. – uchuugaka Jul 05 '13 at 14:03
4

Your method works, but there's something much simpler.

You can use the animator proxy, you just need to tell it how to animate it. To do this, you need to implement the defaultAnimationForKey: method from the NSAnimatablePropertyContainer protocol.


Here's a simple subclass of NSSlider that does this:

#import "NSAnimatableSlider.h"
#import <QuartzCore/QuartzCore.h>

@implementation NSAnimatableSlider

+ (id)defaultAnimationForKey:(NSString *)key
{
    if ([key isEqualToString:@"doubleValue"]) {
        return [CABasicAnimation animation];
    } else {
        return [super defaultAnimationForKey:key];
    }
}

@end

Now you can simply use the animator proxy:

[self.slider.animator setDoubleValue:100.0];

Make sure to link the QuartzCore framework.

IluTov
  • 6,807
  • 6
  • 41
  • 103
  • 1
    According to [this example from Apple](https://developer.apple.com/library/mac/samplecode/AnimatedSlider/Listings/AnimatedSlider_NSSliderExtensions_m.html#//apple_ref/doc/uid/DTS10000378-AnimatedSlider_NSSliderExtensions_m-DontLinkElementID_6), you can also enable this with a class extension. But subclassing is less likely to interfere with other code. – DarkDust Mar 02 '16 at 09:56
0

Here is a Swift version of IluTov answer, setting a floatValue with some animation config:

override class func defaultAnimation(forKey key: NSAnimatablePropertyKey) -> Any? {
    if key == "floatValue" {
        let animation = CABasicAnimation()
        animation.timingFunction = .init(name: .easeInEaseOut)
        animation.duration = 0.2
        
        return animation
    } else {
        return super.defaultAnimation(forKey: key)
    }
}