0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swift:再生スピードが変更可能なキーフレームアニメーション

Posted at

はじめに

RunCatのキーフレームアニメーションのコアな部分の実装について公開.
速度の変わるキーフレームアニメーション(いわゆるパラパラ漫画とかコマ撮りアニメ)の実装方法の文献がなく,非常に厄介だった.

デモ

ezgif.com-gif-maker.gif
このデモのやつは普通にNSViewlayer上でアニメーションしているので簡単だが,メニューバー(NSStatusItem)上で再生するとなると実際にはもっと手の込んだことが必要になる.

ソース

durationを固定して,親レイヤーのspeedを調整するというのがキモ
あとはtimeOffsetbeginTimeの役割把握が重要

AnimationLayer.swift
import Cocoa

class AnimationLayer: CALayer {
    
    //キーフレームアニメーションをするやつ
    private var keyFrameAnimation: CAKeyframeAnimation!

    //何かしらの初期設定(実装によってはいらない)
    public func initialize() {
        self.masksToBounds = true
        self.contentsScale = 2.0
    }
    
    //キーフレームアニメーションの用意
    public func setSequence(_ sequence: [NSImage]) {
        keyFrameAnimation = CAKeyframeAnimation(keyPath: "contents")
        keyFrameAnimation.calculationMode = .discrete //パラパラ漫画形式にするために必須な設定
        keyFrameAnimation.fillMode = .forwards
        keyFrameAnimation.repeatCount = Float.infinity
        keyFrameAnimation.autoreverses = false
        keyFrameAnimation.isRemovedOnCompletion = false
        keyFrameAnimation.beginTime = 0.0
        keyFrameAnimation.values = sequence
        keyFrameAnimation.duration = Double(sequence.count)
    }
    
    //アニメーション開始
    public func startAnimate() {
        if keyFrameAnimation == nil { return }
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        self.add(keyFrameAnimation, forKey: "running")
        CATransaction.commit()
    }

    //アニメーションのスピード変更
    public func updateSpeed(_ speed: Float) {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        self.timeOffset = self.convertTime(CACurrentMediaTime(), from: nil)
        self.beginTime = CACurrentMediaTime()
        self.speed = speed
        CATransaction.commit()
    }
    
}
ViewController.swift
import Cocoa

class ViewController: NSViewController {
    
    let animationLayer = AnimationLayer()
    var timer: Timer? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //アニメーションレイヤーを初期化して追加
        self.view.wantsLayer = true
        animationLayer.initialize()
        self.view.layer!.addSublayer(animationLayer)
        
        //アニメーションするためのコマ画像を用意する
        var icons = [NSImage]()
        for i in (1 ... 4) {
            let icon = NSImage(imageLiteralResourceName: "page" + String(i))
            icons.append(icon)
        }
        //フレームサイズと画像を設定してアニメーション開始
        animationLayer.frame = NSRect(x: 40, y: 40, width: self.view.bounds.width - 80, height: self.view.bounds.height - 80)
        animationLayer.setSequence(icons)
        animationLayer.startAnimate()

        //例として定期的にアニメーションをランダムなスピードに変更
        timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { (t) in
            let speed = Float.random(in: 1 ... 5)
            self.animationLayer.updateSpeed(speed)
        }
    }
    
    override func viewWillDisappear() {
        timer?.invalidate()
    }

    override var representedObject: Any? {
        didSet {
        }
    }

}

備考

一応この方法をベースとしてキーフレームアニメーションを行うことが可能なのだが,Core Animationは勝手にGPUを使える場合は使ってしまうらしく,発熱問題をユーザーから指摘された.GPUを使わずにCPUの消耗が小さい実装方法が知りたい.

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?