NSImageView动画

4

我是Mac开发的新手,我们是否有像中那样的方法,例如imagev = [NSArray arrayWithObjects

我需要类似iOS中的方法在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];

在 iPhone 上,我如何进行动画处理?

敬礼


我觉得你漏掉了一半想要发布的内容... - Thilo
我需要在Mac上做类似于iOS中的事情,如下所示:imageView.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]; - kiri
@kiri:所以,您想从一系列图像中制作单个动画图像,并在Mac上在图像视图中显示它? - Peter Hosey
5个回答

4

我发现有人使用了核心动画方法来解决这个问题,这对我来说已经足够接近了。我稍微修改了一下。你需要@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;
}

然后您可以像这样对其进行动画处理:
- (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];
}

尝试使用简单的计时器来替换图像,结果导致CPU占用率约为90%。使用CoreAnimation将其降低到接近1%。 - bauerMusic
有没有一种方法可以使我的序列只播放一次,以便最后一帧保留? - ixany

2

Ben Flynn 的优秀回答基础上。

在 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)

如果期望视图被调整大小:
删除以下这些行:
let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
layer.frame = layerRect
layer.bounds = layerRect

在添加子图层后,请调用self.needsLayout = true。这将导致调用layout()
//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

最后,重写 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.
}

有没有一种方法可以仅对我的序列进行一次动画处理,以便最后一帧保留? - ixany
我所做的是安排一个计时器,在动画结束时触发(即动画持续时间数字),并删除动画层。此时,您只需呈现所选图像即可。一些CoreAnimation允许在结束时“倒带”或保留最后一帧的动画,但我没有在这个上看到这样的选项。 - bauerMusic

1

Cocoa没有类似于animatedImageWithImages:duration:的东西。Cocoa中的图像可以在空间上(不同的分辨率)和颜色深度上变化,但是不能在时间上变化;单个图像始终是静态图像,从不是动画。

(也许对于动画GIF有例外,但是GIF每帧不能显示超过255或256种颜色,并且不支持部分透明度。此外,我还没有尝试使用NSImage或CGImage机制创建或显示GIF。)

你需要做的是创建一个电影,而不是一张图片。将图像添加到电影中,使每个图像的持续时间不同,以达到所需的播放速度。 然后,在电影视图中显示您的电影,可以选择隐藏控制器

1

Swift 4.2

在添加 beginTime 后,我才成功实现之前的答案。自从 Swift 3 以来,一些常量也发生了变化。因此,我将解决方案转换为了 Swift 4.2。此外,我认为将其创建为 CALayer 扩展会很方便:

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

}
}

使用方法如下:
let sequenceLayer = CALayer.image(sequence: imageSequence, duration: 0.55, frame: yourView.bounds)

0
@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

而在 .m 文件中...

@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

请注意,您需要分配委托(到NSImageView)以调用刷新。
以下是一个示例...
IBOutlet NSImageView *_testGifView;

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

当然,预定计时器可以根据需要进行调整,因为输入时间以百分之一秒为单位(而不是分钟)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接