Swift

[Swift] NSViewAnimationを型安全にしておきました

NSViewAnimationはお手軽にビューの移動アニメーションをやるのに便利なんですが、初期化に使用する辞書の値がAnyで安全ではないので型安全にしておきました。

protocol ViewAnimationTarget {}
extension NSView: ViewAnimationTarget {}
extension NSWindow: ViewAnimationTarget {}

struct ViewAnimationAttributes {

    private(set) var animations: [NSViewAnimation.Key: Any]

    init(target: ViewAnimationTarget, startFrame: NSRect? = nil, endFrame: NSRect? = nil, effect: NSViewAnimation.EffectName? = nil ) {

        animations = [.target: target]
        animations[.startFrame] = startFrame
        animations[.endFrame] = endFrame
        animations[.effect] = effect
    }

    var startFrame: NSRect? {
        get { return animations[.startFrame] as? NSRect }
        set { animations[.startFrame] = newValue }
    }

    var endFrame: NSRect? {
        get { return animations[.endFrame] as? NSRect }
        set { animations[.endFrame] = newValue }
    }

    var effect: NSViewAnimation.EffectName? {
        get { return animations[.effect] as? NSViewAnimation.EffectName }
        set { animations[.effect] = newValue }
    }
}

class ViewAnimation: NSViewAnimation, NSAnimationDelegate {

    var completeHandler: (() -> Void)?

    init(viewAnimations: [ViewAnimationAttributes]) {

        super.init(viewAnimations: viewAnimations.map { $0.animations })
    }

    required init?(coder: NSCoder) {
        fatalError("Can not initialize with NSCoder")
    }

    /// コンプリートハンドラー付き
    func start(completeHandler: @escaping () -> Void) {

        delegate = self
        self.completeHandler = completeHandler

        start()
    }

    ///
    func animationDidEnd(_ animation: NSAnimation) {

        completeHandler?()
        delegate = nil
        completeHandler = nil
    }
}

使用例

let view1 = NSView()

// ここを別々に設定
var animeAttr1 = ViewAnimationAttributes(target: view1)
animeAttr1.startFrame = NSRect(x: 0, y: 0, width: 100, height: 100)
animeAttr1.endFrame = NSRect(x: 100, y: 100, width: 100, height: 100)
animeAttr1.effect = .fadeIn

let view2 = NSView()

// 初期化時に全部設定
let animeAttr2 = ViewAnimationAttributes(target: view2,
                                         startFrame: NSRect(x: 100, y: 100, width: 100, height: 100),
                                         endFrame: NSRect(x: 0, y: 0, width: 100, height: 100),
                                         effect: .fadeOut)

let animation = ViewAnimation(viewAnimations: [animeAttr1, animeAttr2])

animation.duration = 0.5

// コンプリートハンドラー付きstart
animation.start {
    print("Finish animation")
}