kiri
kiri

Reputation: 2007

NSImageView animations

I am new to Mac development, Do we have any methods like imagev = [NSArray arrayWithObjects

I need some thing like what we do in iOS want to do in mac,

imageVie.animationImages = [NSArray arrayWithObjects:
 [UIImage imageNamed:@"1.png"],[UIImage imageNamed:@"2.png"],
 [UIImage imageNamed:@"3.png"],[UIImage imageNamed:@"4.png"],
 [UIImage imageNamed:@"5.png"],[UIImage imageNamed:@"6.png"],
 [UIImage imageNamed:@"7.png"] ,nil];

In iPhone, How can i animate

Regards

Upvotes: 4

Views: 4199

Answers (5)

Samuel Colak
Samuel Colak

Reputation: 1

@interface AnimatedNSImage : NSImage

    @property (nonatomic, retain) IBInspectable NSImageView         *delegate;

    @property (nonatomic, readonly) NSArray                         *frames;
    @property (nonatomic, readonly) CGFloat                         duration;

    - (instancetype) initWithImages:(NSArray*)frames duration:(CGFloat)duration;

@end

And in the .m file...

@interface AnimatedNSImage () {

    NSTimer                         *_scheduledTimer;
    NSArray                         *_frames;

    NSUInteger                      _frameIndex;
    CGFloat                         _duration;

}

@end

@implementation AnimatedNSImage

    @synthesize delegate;

    - (NSArray *) frames
    {
        return _frames;
    }

    - (CGImageRef) CGImage
    {
        if (_frames && _frames.count >0) {
            NSImage *_frame = _frames[_frameIndex];
            return _frame.CGImage;
        }
        return nil;
    }

    - (NSArray<NSImageRep *> *) representations
    {
        NSImage *_frame = _frames[_frameIndex];
        return _frame.representations;
    }

    - (CGFloat) duration
    {
        return _duration;
    }

    - (void) __showNextFrame:(id)sender
    {
        _frameIndex = (_frameIndex + 1) % _frames.count;
        if (delegate) {
            [delegate setNeedsDisplay:YES];
        }
    }

    - (NSSize) size
    {
        if (_frames && _frames.count >0) {
            NSImage *_frame = _frames[_frameIndex];
            return _frame.size;
        }
        return CGSizeZero;
    }

    - (void) setup
    {
        _scheduledTimer = [NSTimer scheduledTimerWithTimeInterval:_duration target:self selector:@selector(__showNextFrame:) userInfo:nil repeats:YES];
    }

    - (void) dealloc
    {
        [_scheduledTimer invalidate];
        _scheduledTimer = nil;
    }

    - (instancetype) initWithImages:(NSArray *)frames duration:(CGFloat)duration
    {
        self = [super init];
        if (self) {
            _frames = frames;
            _duration = duration / 100.0f;
            [self setup];
        }
        return self;
    }

@end

Note that you will have to assign the delegate (to the NSImageView) in order to invoke a refresh..

An example....

IBOutlet NSImageView *_testGifView;

AnimatedNSImage *_animatedImage = [NSImage animatedImageWithAnimatedGIFURL:[NSURL URLWithString:@"https://media.giphy.com/media/B2zB8mHrHHUXS57Cuz/giphy.gif"]];
_testGifView.image = _animatedImage;        
_animatedImage.delegate = _testGifView;

The scheduled timer of course can be adjusted as required as the input time is in centiseconds (as opposed to minutes).

Upvotes: 0

ixany
ixany

Reputation: 6040

Swift 4.2

I couldn’t get the previous answers get to work until I added a beginTime. Since Swift 3 some constants changed too. So I’ve converted the solution to Swift 4.2. Also, I thought it would be handy to create it as a CALayer extention:

extension CALayer {  

static func image(sequence: [NSImage], duration: CFTimeInterval? = nil, frame: CGRect? = nil) -> CALayer {

    let layer = CALayer()
    if let f = frame { layer.frame = f }
    layer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]

    let keyPath = "contents"

    let keyFrameAnimation = CAKeyframeAnimation(keyPath: keyPath)
    keyFrameAnimation.values = sequence
    keyFrameAnimation.calculationMode = .discrete
    keyFrameAnimation.fillMode = .forwards
    keyFrameAnimation.duration = duration ?? CFTimeInterval(sequence.count / 18)
    keyFrameAnimation.repeatCount = Float.infinity
    keyFrameAnimation.autoreverses = false
    keyFrameAnimation.isRemovedOnCompletion = false
    keyFrameAnimation.beginTime = 0.0

    layer.add(keyFrameAnimation, forKey: keyPath)

    return layer

}
}

Use it like

let sequenceLayer = CALayer.image(sequence: imageSequence, duration: 0.55, frame: yourView.bounds)

Upvotes: 1

bauerMusic
bauerMusic

Reputation: 6156

Building on the great answer by Ben Flynn.

In Swift 3:

// This needs to happen around init.
// NSView (and who inherit from it) does not come with a layer.
// self.layer will be nil at init.
self.layer = CALayer()
self.wantsLayer = true

let layer = CALayer()
let keyPath = "contents" // (I did not find a constant for that key.)
let frameAnimation = CAKeyframeAnimation(keyPath: keyPath)
frameAnimation.calculationMode = kCAAnimationDiscrete

// Duration
// This is the duration of one cycle
let durationOfAnimation: CFTimeInterval = 2.5
frameAnimation.duration = durationOfAnimation
frameAnimation.repeatCount = HUGE// can also use Float.infinity

let imageSeq: [NSImage] = imageSequance // Your images to animate
frameAnimation.values = imageSeq

// Sadly, there are no layout constraints on CALayer.
// If your view will be resized while animating, you'll need to override
// 'func layout()' and calculate aspect ratio if needs be
let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
layer.frame = layerRect
layer.bounds = layerRect
layer.add(frameAnimation, forKey: keyPath)

self.layer?.addSublayer(layer)

If the view is expected to be resized:

Remove these lines:

let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
layer.frame = layerRect
layer.bounds = layerRect

And call self.needsLayout = true after adding the sublayer. This will cause the layout() to be called.

//let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
//layer.frame = layerRect
//layer.bounds = layerRect
layer.add(frameAnimation, forKey: keyPath)

self.layer?.addSublayer(layer)
self.needsLayout = true

Lastly, override layout():

override func layout() {
    super.layout()

    var layerFrame = CGRect(origin: CGPoint.zero, size: self.frame.size)
    self.myLayer.frame = layerFrame
    self.myLayer.bounds = // Adjust ratio as needed.
}

Upvotes: 2

Ben Flynn
Ben Flynn

Reputation: 18922

I found someone using a Core Animation approach to this issue which was close enough for me. I modified it slightly. You need to @import QuartzCore;

- (void)awakeFromNib
{
    CALayer *layer = [CALayer layer];
    NSMutableArray *spinnerImages = [NSMutableArray arrayWithCapacity:30u];
    for (NSUInteger i = 0; i < 30; ++i)
    {
        NSString *imageName = [NSString stringWithFormat:@"spinner%@", @(i)];
        [spinnerImages addObject:[NSImage imageNamed:imageName]];
    }
    self.spinnerImages = spinnerImages;
    layer.frame = self.imageView.bounds;
    [self.imageView setLayer:layer]; // This view is just a container for the layer. Its frame can be managed by a xib.
    self.imageView.wantsLayer = YES;

    self.spinnerLayer = layer;
}

Then you can animate it like this:

- (void)stopAnimating
{
    if ([self.layer.animationKeys containsObject:kAnimationKey])
    {
        [self.layer removeAnimationForKey:kAnimationKey];
    }
}

- (void)startAnimating
{
    if ([self.layer.animationKeys containsObject:kAnimationKey])
    {
        return;
    }
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:kAnimationKey];
    [animation setCalculationMode:kCAAnimationDiscrete];
    [animation setDuration:1.0f];
    [animation setRepeatCount:HUGE_VALF];
    [animation setValues:self.spinnerImages];
    [self.spinnerLayer addAnimation:animation forKey:kAnimationKey];
}

Upvotes: 4

Peter Hosey
Peter Hosey

Reputation: 96323

Cocoa doesn't have anything like animatedImageWithImages:duration:. Images in Cocoa can vary in space (different resolutions) and color depth, but not time; a single image is always a static image, never animated.

(There might be an exception for animated GIFs, but GIFs can't display more than 255 or 256 colors per frame, and do not support partial transparency. Moreover, I haven't tried creating or displaying GIFs using the NSImage or CGImage machinery.)

What you'll need to do is create not an image, but a movie. Add images to the movie, varying each one's duration to achieve the playback speed you want. Then, display your movie in a movie view, optionally with the controller hidden.

Upvotes: 1

Related Questions