Fun with CAShapeLayer

This post written by Guanshan Liu
Guanshan is an iOS Developer currently working as an iOS Engineer at Alibaba Inc. Before working at Alibaba, Guanshan worked with 2K Games on Civilization Revolution 1 and 2 for iOS. He has a Masters in Software Engineering from the University of York, UK as well as a Bachelors of Engineering in Information Security from Nanjing University of Aeronautics and Astronautics. Find him on twitter, @guanshanliu.
 

CAShapeLayer is a specialized subclass of CALayer that draws itself using the shape you define via the path property. path is an instance of CGPath. We could leverage the convenient UIBezierPath APIs to create a path, and then retrieve the CGPath from it. Besides all the animatable properties inherited from CALayer, there are other animatable properties that let you control attributes such as fill color, stroke color, line thickness, etc. In this tutorial, I will illustrate some visual effects using these properties.


Looking for something more in-depth? Try my book & video courses
Get The Swift Book
Learn About My Book & Video Packages »

Let’s Get Started!

First, you need to download the starter project from here.

Rounded Corners

There are different ways to draw rounded rectangles.

Go to RoundedCornersViewController class, add the following codes at the end of viewDidAppear method.

// 1
rectShape1.backgroundColor = UIColor.redColor().CGColor
rectShape1.cornerRadius = 20
// 2
rectShape2.fillColor = UIColor.greenColor().CGColor
rectShape2.path = UIBezierPath(roundedRect: rectShape2.bounds, cornerRadius: 20).CGPath
// 3
rectShape3.fillColor = UIColor.blueColor().CGColor
rectShape3.path = UIBezierPath(roundedRect: rectShape3.bounds, byRoundingCorners: .BottomLeft | .TopRight, cornerRadii: CGSize(width: 20, height: 20)).CGPath

1. The first way to draw a rounded rectangle is to change a layer’s cornerRadius property. This applies to all CALayerS.
2. We could also use path to draw a rounded rectangle. Assign a rounded rectangle path via this convenient method on UIBezierPath. By doing this, we have to use fillColor instead of backgroundColor. Because backgroundColor is color of the layer’s background, while fillColor is the color used to fill the shape’s path.
3. Using path, we are not limited to round all corners. We could specify which corner we want to round. In this example, I only change bottom left and top right corners.

Run and select Rounded Corners cell.

Path Animation

path is also an animatable property. We could achieve the basic Material-Design-feel effect by animating it.

Go to PathViewController class, add the following codes at the end of viewDidAppear method.

// fill with yellow
rectShape.fillColor = UIColor.yellowColor().CGColor

// 1
// begin with a circle with a 50 points radius
let startShape = UIBezierPath(roundedRect: bounds, cornerRadius: 50).CGPath
// animation end with a large circle with 500 points radius
let endShape = UIBezierPath(roundedRect: CGRect(x: -450, y: -450, width: 1000, height: 1000), cornerRadius: 500).CGPath

// set initial shape
rectShape.path = startShape

// 2
// animate the `path`
let animation = CABasicAnimation(keyPath: "path")
animation.toValue = endShape
animation.duration = 1 // duration is 1 sec
// 3
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // animation curve is Ease Out
animation.fillMode = kCAFillModeBoth // keep to value after finishing
animation.removedOnCompletion = false // don't remove after finishing
// 4
rectShape.addAnimation(animation, forKey: animation.keyPath)

1. Calculate begin and end shapes for the animation. Then assign the startShape to path.
2. Use CABasicAnimation to animate path. Destination value for path is the end shape we defined before. Set it to toValue. Then set the animation duration to 1 second.
3. Set the animation curve to ease out, making it look more natural. With removedOnCompletion set to false, fillMode to kCAFillModeBoth, when the animation finishes, rectShape will remain the end shape.
4. Add the animation to the layer.

Run and select path Animation cell. See the animation:

View Movie

Line Width Animation

lineWidth defines the stroke line width of the shape’s path, and it’s also animatable. There are some cool effects we could make via lineWidth.

Go to LineWidthViewController class, add the following codes at the end of viewDidAppear method.

// setup
let rect = CGRect(x: 0, y: 0, width: view.bounds.width, height: 1)
rectShape.bounds = rect
rectShape.position = view.center
rectShape.path = UIBezierPath(rect:rect).CGPath

// 1
rectShape.lineWidth = 10
rectShape.strokeColor = UIColor.blueColor().CGColor

// animate
let animation = CABasicAnimation(keyPath: "lineWidth")
// 2
animation.toValue = 1000
animation.duration = 1 // duration is 1 sec
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // animation curve is Ease Out
animation.fillMode = kCAFillModeBoth // keep to value after finishing
animation.removedOnCompletion = false // don't remove after finishing
rectShape.addAnimation(animation, forKey: animation.keyPath)

1. Give an initial line width of 10 points. To set the line color, we use strokeColor.
2. Similar to path animation, animate line width to 1000.

Run and select lineWidth Animation cell. See the animation:

Stroke Animation

strokeStart and strokeEnd defines the relative location at which to begin stroking the path, ranging from 0 to 1. Many cool activity indicator can be made by using them. Here is a simple example that show you how to animate these properties.

Go to StrokeViewController class, add the following codes at the end of viewDidAppear method.

// 1
rectShape.path = UIBezierPath(ovalInRect: rectShape.bounds).CGPath

rectShape.lineWidth = 4.0
rectShape.strokeColor = UIColor.lightGrayColor().CGColor
rectShape.fillColor = UIColor.clearColor().CGColor

// 2
rectShape.strokeStart = 0
rectShape.strokeEnd = 0.5

// 3
let start = CABasicAnimation(keyPath: "strokeStart")
start.toValue = 0.7
let end = CABasicAnimation(keyPath: "strokeEnd")
end.toValue = 1

// 4
let group = CAAnimationGroup()
group.animations = [start, end]
group.duration = 1.5
group.autoreverses = true
group.repeatCount = HUGE // repeat forver
rectShape.addAnimation(group, forKey: nil)

1. Here is another way to draw a circle using another UIBezierPath’s convenient initializer.
2. Set the initial values for strokeStart and strokeEnd.
3. Create animations like before.
4. Group two animation together. Because we want to both animations to happen simultaneously. The duration is 1.5 seconds. It will auto reverse the animations upon finishing. And it will repeat forever.

Run and select Stroke Animation cell. See the animation:

Conclusions

You could find the complete project on Github. If you have further questions, you can leave a comment or ask me on Twitter.

Follow me on Twitter


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS

This entry was posted in Swift, Uncategorized and tagged , by Guanshan Liu. Bookmark the permalink.

About Guanshan Liu

Guanshan is an iOS Developer currently working as an iOS Engineer at Alibaba Inc. Before working at Alibaba, Guanshan worked with 2K Games on Civilization Revolution 1 and 2 for iOS. He has a Masters in Software Engineering from the University of York, UK as well as a Bachelors of Engineering in Information Security from Nanjing University of Aeronautics and Astronautics. Find him on twitter, @guanshanliu.

Comment