[Swift]虹色のボーダーアニメーションを作成する

#はじめに
今回は使い所は限られるとは思いますが、虹色ボーダーラインのアニメーションを実装します。

こんな感じ↓

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Swiftで虹色のボーダーラインのアニメーション <a href="https://t.co/tzrhSnnebS">pic.twitter.com/tzrhSnnebS</a></p>&mdash; M (@p_x9) <a href="https://twitter.com/p_x9/status/1383707861411188750?ref_src=twsrc%5Etfw">April 18, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


#本編
##1. 虹色のCGColor配列を作成
虹色の配列を作成します。色相、彩度、明度のうち、色相を少しずつ変化させればなだらかな虹色が作成できます。
後々のためここではUIColorではなくCGColorの配列としておきます。

```swift
var rainbow: [CGColor] {
    let increment: CGFloat = 0.02

    return [CGFloat](stride(from: 0.0, to: 1.0, by: increment)).map{ hue in 
        UIColor(hue: hue, saturation: 1.0, brightness: 1.0, alpha: 1.0).cgColor
    }
}
```

##2. 虹色のグラデーションを作成する
まず虹色のグラデーションのレイヤを作成します。

グラデーションのレイヤを作成するには`CAGradientLayer`を使います。
ここで`CAGradientLayer`のcolorsプロパティに先ほど作成した虹色の配列を適用することで、虹色グラデーションのレイヤができます。

```swift
let gradientLayer = CAGradientLayer()
gradientLayer.colors = rainbow
```

今回はラベルに虹色のボーダーラインアニメーションを追加しますが、そのフレームに対し少しマージンを取ります。

```swift
 gradientLayer.frame = CGRect(x: -4, y: -4, width: label.frame.width+8, height: label.frame.height+8)
```

グラデーションの始まりと終わりのポイントを指定します。
今回は左上から右下にかけてのグラデーションにしたいので、以下のように設定しました。この時、x,yともに0から1の範囲で指定する必要があるので注意が必要です。

```swift
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
```

こんな感じです。
![スクリーンショット 2021-04-18 18.31.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377767/cc54e2a7-5bc8-d70c-23e4-7501f5e99add.png)

##3. アニメーションを行うレイヤの作成
`CAShapeLayer`を使用します。
まずボーダラインのpathを指定します。

```swift
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: label.frame.width+8, height: label.frame.height+8))
shapeLayer.path = path.cgPath
```

また`fillColor`は透明にしておきます。線の太さも決めておきます。

```swift
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 8.0
```

##4. maskの設定
グラデーションのレイヤにmaskとして3で作成したアニメーションを行うレイヤを設定します。
四角いグラデーションのレイヤから、maskに設定したレイヤによってくり抜くといったイメージです。

##5. レイヤをviewに適用
今回はlabelに対して設定します。

```swift
label.layer.addSublayer(shapeLayer)
```

##アニメーションの作成
`CABasicAnimation`を使用します。CAShapeLayerのstrokeEndプロパティを0から1までアニメーションさせたいので以下のように実装します。
また、アニメーションの時間も設定しておきます。

```swift
let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.strokeEnd))
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = 3.0
```

ここではアニメーションを無限にループさせたいので以下のように設定しておきます。

```swift
animation.repeatDuration  = .infinity
```

##6. animationのレイヤへの適用
以下を実行するとアニメーションが始まります。

```swift
self.shapeLayer.add(animation, forKey: #keyPath(CAShapeLayer.strokeEnd))
```

##7.完成
これで完成です。

##動作原理
まず、`CAGradientLayer`でグラデーションのレイヤを作ります。
`mask`プロパティに別レイヤを設定すると、設定したレイヤの形にくり抜くことができます。
そこで、`CAShapeLayer`で外枠となる部分のレイヤを作成し、`mask`として適用することでグラデーションの外枠ができます。
`CAShapeLayer`の`strokeStart`と`strokeEnd`が外枠の始まりと終わりの部分を表すのでこと値をアニメーションで徐々に変化させることで、外枠がアニメーションし目的を達成することができます。

#おわりに
読んでいただいてありがとうございます。初めて二日連続で記事を書きました。参考になれれば嬉しいです。
ご指摘ご質問等ございましたら、コメントまたはDMまでお願いします。
本記事での内容は[こちらのリポジトリ](https://github.com/p-x9/RainbowBorderAnimation)よりダウンロードできます。
簡単に実行できるので良かったら試してみてください

#追記
##2021/04/19
この記事に[関連した記事](https://qiita.com/p_x9/items/5fbe76748ee060f18daa)を書きました。
合わせて読んでいただけると嬉しいです
https://qiita.com/p_x9/items/5fbe76748ee060f18daa