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.
Let’s Get Started!
First, you need to download the starter project from here.
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 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:
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:
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: