Reputation: 2007
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
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
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
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
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
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