畫面正上方放著一個存錢筒,點下「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
}
}
}
}