

畫面正上方放著一個存錢筒,點下「Show me the money」以後,會有300個金幣、銀幣從畫面底部飛向存錢筒。
錢幣飛行的過程會逐漸變小,到達存錢筒的位置時會消失。

讓 bankView 根據 Z 軸進行來回三次的 rotation 的動畫。
fileprivate func shakeBank() {
    let shake = CABasicAnimation(keyPath: "transform.rotation.z")
    shake.fromValue = -0.2
    shake.toValue = 0.2
    shake.duration = 0.1
    shake.autoreverses = true
    shake.repeatCount = 3
    
    bankView.layer.add(shake, forKey: "bankShakeAnimation")
}
錢幣的動畫是這樣的,一開始在畫面底部生成 300個金幣、銀幣,接著為每一個錢幣延遲做一個動畫,飛向存錢筒。
通過 Dispatch.main.asyncAfter 方法延遲每一個初始化的金幣及其動畫。
根據順序為錢幣分配 tag 和錢幣圖案的分配(金幣或銀幣)
這時候錢幣的 center 位置,是存錢筒的 center 偏上 20 個單位。
fileprivate func startCoinanimation() {
    fireButton.isHidden = true
    lastFinishedCoinNumber = 0
    for i in 0...totalCoinCount {
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.01, execute: {
            self.initCoinWith(number: i)
        })
    }
}
fileprivate func initCoinWith(number:Int) {
    let coinImageName = "icon_coin_\(number % 2 + 1)"
    let coinView = UIImageView(image: UIImage(named:coinImageName))
    let x = bankView.center.x
    let y = bankView.center.y - 20
    coinView.center = CGPoint(x: x , y: y)
    
    // plus 1 since view's tag default is 0 includingUIViewController.view's tag is 0
    coinView.tag = number + 1
    
    coinNumbers.append(coinView.tag)
    animate(coinView: coinView)
    view.addSubview(coinView)
}
這裡分步驟解釋一下,動畫都在下面這個方法裡面。
 fileprivate func animate(coinView:UIView){}
剛才我們給 coinView 的 center 設定的是希望最後停留的位置,這裡紀錄做 targetX / targetY
let targetX = coinView.layer.position.x
let targetY = coinView.layer.position.y
let path = CGMutablePath()
let fromX = CGFloat(arc4random() % 320)
let fromY = CGFloat(arc4random() % UInt32(targetY))
let height = UIScreen.main.bounds.height + coinView.frame.size.height
let cpx = targetX + (fromX - targetX)/2
let cpy = fromY / 2 - targetY
// position where animation start
path.move(to: CGPoint(x: fromX, y: height))
path.addQuadCurve(to: CGPoint(x:targetX, y:targetY), control: CGPoint(x: cpx, y: cpy))
let positionAnimation = CAKeyframeAnimation(keyPath: "position")
positionAnimation.path = path
// animate from big to small
let from3DScale:CGFloat = 1 + CGFloat(arc4random() % 10) * 0.1
let to3DScale:CGFloat = from3DScale * 0.5
let scaleAniamtion = CAKeyframeAnimation(keyPath: "transform")
scaleAniamtion.values = [
    CATransform3DMakeScale(from3DScale, to3DScale, from3DScale),
    CATransform3DMakeScale(to3DScale, to3DScale, to3DScale)
]
scaleAniamtion.timingFunctions = [
    CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut),
    CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
]
將錢幣移動和縮放大小的動畫合在一起,交給 coinView.layer
// combine animations
let animationGroup = CAAnimationGroup()
animationGroup.delegate = self
animationGroup.duration = animationDuration
animationGroup.fillMode = kCAFillModeForwards
animationGroup.isRemovedOnCompletion = false
animationGroup.animations = [positionAnimation, scaleAniamtion]
coinView.layer.add(animationGroup, forKey: "coin animation group")
CAAnimationGroup 有個代理方法,當動畫完成的時候會執行 animationDidStop()
我們通過 lastFinishedCoinNumber 來紀錄最後完成錢幣 (coinView) 動畫的 tag
通過 tag 移出對應的 coinView 並且從 coinNumbers 中移出號碼。
在所有錢幣的動畫都完成時,搖動存錢筒。
extension HomeViewController:CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if flag {
            lastFinishedCoinNumber += 1
            view.viewWithTag(coinNumbers.first!)?.removeFromSuperview()
            coinNumbers.removeFirst()
            
            if lastFinishedCoinNumber == totalCoinCount {
                shakeBank()
                fireButton.isHidden = false
            }
            
        }
    }
}