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

# やりたかったこと

<img width="200" alt="ezgif-3-ee9b3d4e25c3.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363868/e5adafe0-fde8-6b68-821e-4a57344b572d.gif">

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

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

# 実装

```swift
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
    }

}

```


ポイントとなるのは、

```swift
arcLayer.frame = CGRect(x: self.view.center.x-50, y: self.view.center.y-50, width: 100, height: 100)
```
`CAShapeLayer`の`frame`と、

```swift
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`の座標が基準となるためそこも注意が必要です。

# 終わりに

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

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