LoginSignup
2
3

More than 3 years have passed since last update.

[swift] UIBezierPath, CAShapeLayerで描画した扇型をCABasicAnimationで回転させる

Last updated at Posted at 2019-04-28

やりたかったこと

ezgif-3-ee9b3d4e25c3.gif

こういう風に描画した扇型が回転するアニメーション。

調べてみると、円グラフやインジケータのように、始まりは固定で徐々に円が完成していくようなアニメーションの記事はいくつかあったのですが、自分のやりたかったことはなかなか見つからなかったのでメモしておきたいと思います。

実装

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)

CAShapeLayerframeと、

path.addArc(withCenter: arcCenter,
                    radius: rad,
                    startAngle: start,
                    endAngle: end,
                    clockwise: false)

この時のwithCenter:です。

自分はここで躓いて結構な時間を取られました......。

まず、当たり前かもしれませんがCAShapeLayerframeは何も設定しないと(0.0, 0.0, 0.0, 0.0)になるようで、そのままアニメーションさせようとしても基準が画面左上の原点になってしまいます。

また、UIBezierPathwithCenter:に関してはCAShapeLayerの座標が基準となるためそこも注意が必要です。

終わりに

今回のことで、思い通りのアニメーションをさせるには基準となる座標にも気を配らなければならないということを学びました。

間違いや、もっとこうした方が良いなどありましたら、コメントにて教えていただけると嬉しいです!

2
3
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
2
3