やりたかったこと
こういう風に描画した扇型が回転するアニメーション。
調べてみると、円グラフやインジケータのように、始まりは固定で徐々に円が完成していくようなアニメーションの記事はいくつかあったのですが、自分のやりたかったことはなかなか見つからなかったのでメモしておきたいと思います。
実装
import UIKit
enum rotate {
case right
case left
}
class CircleAnimationViewController: UIViewController {
let arcLayer = CAShapeLayer()
let circleLayer = CAShapeLayer()
var currentPosition = 0
var rad: CGFloat = 100.0
func drawArc() {
let angle: CGFloat = CGFloat(2.0 * Double.pi / 6)
let start: CGFloat = CGFloat(-Double.pi / 2.0) - angle * CGFloat(currentPosition) // 開始の角度
let end: CGFloat = start - angle // 終了の角度
let path: UIBezierPath = UIBezierPath()
let arcCenter: CGPoint = CGPoint(x: 50, y: 50)
let circlePath: UIBezierPath = UIBezierPath();
circlePath.move(to: arcCenter)
circlePath.addArc(withCenter: arcCenter,
radius: rad,
startAngle: 0,
endAngle: CGFloat(-Double.pi * 2.0),
clockwise: false)
circleLayer.frame = CGRect(x: self.view.center.x-50, y: self.view.center.y-50, width: 100, height: 100)
circleLayer.fillColor = UIColor.lightGray.cgColor
circleLayer.path = circlePath.cgPath
self.view.layer.addSublayer(circleLayer)
path.move(to: arcCenter)
path.addArc(withCenter: arcCenter,
radius: rad,
startAngle: start,
endAngle: end,
clockwise: false)
arcLayer.frame = CGRect(x: self.view.center.x-50, y: self.view.center.y-50, width: 100, height: 100)
arcLayer.fillColor = UIColor.black.cgColor
arcLayer.path = path.cgPath
self.view.layer.addSublayer(arcLayer)
}
private func rotateArc(_ direction: rotate) {
let angle: CGFloat = CGFloat(2.0 * Double.pi / 6)
var fromVal: CGFloat = angle * CGFloat(currentPosition)
var toVal: CGFloat = angle * CGFloat(currentPosition+1)
if direction == .right {
fromVal = -angle * CGFloat(currentPosition)
toVal = -angle * CGFloat(currentPosition+1)
} else {
fromVal = -angle * CGFloat(currentPosition)
toVal = -angle * CGFloat(currentPosition-1)
}
let animation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation")
animation.isRemovedOnCompletion = false
animation.fillMode = CAMediaTimingFillMode.forwards
animation.fromValue = fromVal
animation.toValue = toVal
animation.duration = 0.2
arcLayer.add(animation, forKey: "animation")
}
override func viewDidLoad() {
super.viewDidLoad()
drawArc()
}
@IBAction func backBtnTapped(_ sender: Any) {
rotateArc(.right)
currentPosition += 1
}
@IBAction func nextBtnTapped(_ sender: Any) {
rotateArc(.left)
currentPosition -= 1
}
}
ポイントとなるのは、
arcLayer.frame = CGRect(x: self.view.center.x-50, y: self.view.center.y-50, width: 100, height: 100)
CAShapeLayer
のframe
と、
path.addArc(withCenter: arcCenter,
radius: rad,
startAngle: start,
endAngle: end,
clockwise: false)
この時のwithCenter:
です。
自分はここで躓いて結構な時間を取られました......。
まず、当たり前かもしれませんがCAShapeLayer
のframe
は何も設定しないと(0.0, 0.0, 0.0, 0.0)
になるようで、そのままアニメーションさせようとしても基準が画面左上の原点になってしまいます。
また、UIBezierPath
のwithCenter:
に関してはCAShapeLayer
の座標が基準となるためそこも注意が必要です。
終わりに
今回のことで、思い通りのアニメーションをさせるには基準となる座標にも気を配らなければならないということを学びました。
間違いや、もっとこうした方が良いなどありましたら、コメントにて教えていただけると嬉しいです!