There was a girl I liked. I did not know how to draw, sing or say cute things to impress her. I decided to make something cute the only way I knew how - by doing animations on iOS. This is the result:

Drawing curves

To create an animation of two drawings like this, we'll be using Bézier curves. The API for this on iOS is UIBezierPath. The UIBezierPath object is a model object for storing the points, curves and lines of our drawing. To display this, we will be using a CALayer subclass called CAShapeLayer.

UIBezierPath *path = [[UIBezierPath alloc] init];
// add points to the path
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor magentaColor];
shapeLayer.fillColor = [[UIColor magentaColor] colorWithAlphaComponent:0.5f];
shapeLayer.path = path;

To install the shapeLayer into the view hierarchy we either -addSublayer: it to a view.layer of any view or we create a wrapper UIView where we subclass the +layerClass method like this:

+(Class)layerClass {
    return [CAShapeLayer class];

And then the self.layer of the UIView is guaranteed to be a CAShapeLayer.


Unfortunately, while many CALayer properties are implicitly animatable, the path property isn't. We have to create our own CABasicAnimation and parametrize it for the animation to take place.

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.fromValue = heartPath.CGPath;
animation.toValue = textPath.CGPath;
[shapeLayer addAnimation:animation forKey:nil];

This code will animate the shapeLayer once.


To make the animation interactive we'll be using the CAMediaTiming protocol. The documentation is very general here:

The CAMediaTiming protocol models a hierarchical timing system, with each object describing the mapping of time values from the object's parent to local time.

This protocol basically lets us play Chronos (or Sailor Pluto). We can set the speed of animations, e.g. speeding them up 5 times or making them not progress by themselves. We can also control the progress in animation with properties.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.animation.duration = 1.0f;
    self.animation.speed = 0.0f;

- (void)sliderValueChanged:(UISlider *)slider {
    self.animation.timeOffset = slider.value

In this sample we use the previous UIBezierPath animation. We set the speed to 0, so it won't do anything by itself. We set the duration to 1 so the possible values of slider.value will map perfectly to animation progress.
We manually set the progress of the animation using timeOffset using the slider callback.

Better animation

There's another trick to make it better (don't mind the quality):

To make it look like this you have to remember to balance the number of segments in both UIBezierPaths. Just make sure that the number of -addLineToPoint: and
-addCurveToPoint:controlPoint1:controlPoint2: for each UIBezierPath is roughly equal. Then every segment of the first path will animate into a segment in the second path, creating a better quality animation.

The heart path in the first version had only 2 curves, whereas the word Nikoleta had a lot of them. We can see that the heart grows from the first segment of the first letter of the word.


To create those complex Bezier paths you can use an app that will generate them for you:


The girl I did this for thought it was really cute. She is now my fiancée and we're happy together. I hope you will find your second half too.