Swift CAShapeLayerで図形にグラデーションをつける方法

tail -f my tech life.
9 min readNov 15, 2017

--

UIViewにCAShapeLayerを投入し図形を表示できる。CAShapeLayerのプロパティには、fillColor(背景を塗る色指定)やstrokeColor(図形の輪郭を書く時の色指定)が用意されているので、簡単に図形に色を塗ることができる。

ではCAShapeLayerで背景色にグラデーションをつける場合はどうするのか?

毎回忘れるのと誤解していたので整理がてらメモ。

CALayerのmask

グラデーションを付ける前にCALayerのmaskの機能を見てみる。

mask — CALayer | Apple Developer Documentation

var mask: CALayer? { get set } 

これは、別のCALayerを mask にセットすると、もともとのCAlayerの図形が「マスク」され部分的に表示されるという機能である。

maskサンプル

CAShapeLayerにmaskを指定したときの表示例

これはmaskを利用した例。赤い星のCAShapeLayerに 黒丸4つのCAShapeLayerをmaskとして設定すると、星の一部が切り抜かれて表示される。

星の形とmaskとの重なっている部分

黒丸4つのマスクと重なった部分が表示されたのだ。

以下Swiftコード。

  • 黒丸マスク CAShapeLayer
// mask path
let maskpath = CGMutablePath()
maskpath.addEllipse(in: CGRect(x: 10, y: 10, width:30, height:30))
maskpath.addEllipse(in: CGRect(x: 10, y: 60, width:30, height:30))
maskpath.addEllipse(in: CGRect(x: 60, y: 10, width:30, height:30))
maskpath.addEllipse(in: CGRect(x: 60, y: 60, width:30, height:30))
// show path on layer
let maskLayer = CAShapeLayer()
maskLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
maskLayer.path = maskpath
maskLayer.fillColor = UIColor.black.cgColor
  • 星マスク CAShapeLayer
// create path of ☆
let starPath = CGMutablePath()
starPath.move(to: CGPoint(x:19.098,y:100))
starPath.addLine(to: CGPoint(x:25,y:63.82))
starPath.addLine(to: CGPoint(x:-0,y:38.197))
starPath.addLine(to: CGPoint(x:34.549,y:32.918))
starPath.addLine(to: CGPoint(x:50,y:-0))
starPath.addLine(to: CGPoint(x:65.451,y:32.918))
starPath.addLine(to: CGPoint(x:100,y:38.197))
starPath.addLine(to: CGPoint(x:75,y:63.82))
starPath.addLine(to: CGPoint(x:80.902,y:100))
starPath.addLine(to: CGPoint(x:50,y:82.918))
starPath.closeSubpath()
// show path on layer
let shapeLayer = CAShapeLayer()
shapeLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
shapeLayer.path = starPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.borderColor = UIColor.gray.cgColor
  • 星マスクに黒丸マスクをつけて UIViewに表示
shapeLayer.mask = maskLayer
view.layer.addSublayer(shapeLayer)

アニメーションするmask

今度はさっきの赤い星に、アニメーションする円をmaskとして指定してみよう。

アニメーションするCAShapeLayer

期待通り、星型と円が重なるものがアニメーション表示された。

星型のCAShapeLayerにアニメーションするmaskを指定した結果
  • アニメーションする円のSwiftコード
// mask path
let maskpath = CGMutablePath()
maskpath.addEllipse(in: CGRect(x: 50, y: 50, width: 0, height: 0))
let maskpath2 = CGMutablePath()
maskpath2.addEllipse(in: CGRect(x: 0, y: 0, width: 100, height: 100))
let maskLayer = CAShapeLayer()
maskLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
maskLayer.path = maskpath
maskLayer.fillColor = UIColor.black.cgColor// アニメーション指定
let pathKeyframe = CAKeyframeAnimation(keyPath: "path")
pathKeyframe.values = [maskpath,maskpath2,maskpath]
pathKeyframe.keyTimes = [0, 0.5, 1]
pathKeyframe.duration = 3.0
pathKeyframe.repeatDuration = CFTimeInterval.infinity
pathKeyframe.isRemovedOnCompletion = false
maskLayer.add(pathKeyframe, forKey: "path")

それで、図形にグラデーションをつける方法は?

勘の良い人ならこれでおわかりかもしれない。 グラデーションを描くCAGradientLayerというCALayerがある。そのCAGradientLayerのmaskに希望する形のCALayerを指定すればよいのだ。

CAGradientLayerでグラデーション描くとこうなった。

  • CAGradientLayer グラデーションのSwiftコード
let gradLayer = CAGradientLayer()
gradLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
gradLayer.colors = [
UIColor.red.cgColor,
UIColor.blue.cgColor,
]
view.layer.addSublayer(gradLayer)

じゃあ、このグラデーションをマスクしちゃえばよさそう。

gradLayer.mask = maskLayer
CAGradientLayer にmask指定した結果。

同様に星型のレイヤをgradLayer.mask に指定すれば、星型に抜くことができるし、アニメーションもmaskに指定することができる!

何を勘違いしていたか

作画ソフトを使うと図形にグラデーションをつけるというイメージなので、CAShapeLayerにグラデーション指定用のプロパティがあるかと思っていました…グラデーションをマスクするのですね。どうもこのあたりに違和感がありましたが、きちんと調べたら理解できました。

もちろんCALayerやUIViewを自前で描画する方法もあると思いますが、一例として。

参考

--

--

tail -f my tech life.

iOSアプリを作っている、とある技術者のブログです。SwiftやiOS話題多いです。