What’s inside LiquidLoader library ?

Today I reviewed https://github.com/yoavlt/LiquidLoader library and be curious not only about how it can achieve that liquid effect but also how it can achieve that effect in an effective performance.

The ambition is big, the first action to look at the code lines that drawing the liquid is reasonable. But first be calm, step back and look an overview.

Look overview at LiquidLoader library

I walk around the classes structure and see LiquidLoader is just Adapter class for the Effects module. Main effects is in LiquidLoadEffect base-class and it’s subclasses. Effect enum is the Factory for real effects.

LiquidLoadEffect define the main flow to setup UI hierarchy ( in setup:) and main flow to drawing runloop timer (in update:) but leave detail implementation for it’s subclasses (in // abstract methods).

The liquid drawing is achieved by LiquidLoadEffect subclasses that composite UIView layers (which is LiquittableCircle and CAShapeLayer) to build UI hierarchy.

The main drawing put at updateKeyframe: and SimpleCircleLiquidEngine class.

Now look at how animation happen and look at main update cycle in updateKeyframe: .

Why not use NSTimer to animate?

To animate in iOS, there’re two ways: use Core Animation block animateWithDuration: animations: or use NSTimer.

The liquid don’t have an target frame, so obviously we should use the timer. But LiquidLoader don’t use NSTimer with an fixed interval to avoid the issue of stuttered animation . According to Ben at http://www.bigspaceship.com/ios-animation-intervals/ , the update interval might not be syncing up with iOS redrawing the screen. This could result in an update (from your own timer interval) occasionally being called twice, but only being drawn once or vice versa (drawn: get called when UIKit’s timer interval reach).

Instead LiquidLoader use an timer that syncing up with iOS redrawing the screen.

timer = CADisplayLink(target: self, selector: #selector(LiquidLoadEffect.update))
timer?.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)

The main job of updateKeyframe: is update the position of moving circle and delegate to the engine to draw liquid shapes.

Maybe you’re curious about what is keyframe and why LiquidLineEffect and LiquidCircleEffect classes calculate key in different ways. Please calm down and keep reading until the end :)

Now look at how liquid shapes get drawn.


Reading SimpleCircleLiquidEngine class is not fun at all :( . In order to see how it work, I added some code:

//in LiquidLineEffect class, I put at the end of update method.
// 1.6 is a magic number, pls don't change it :)
key = 1.6
//hard-code the color in constructLayer method 
//of SimpleCircleLiquidEngine class
shape.fillColor = UIColor.red.cgColor

The result is:

drawing result of SimpleCircleLiquidEngine class

Yep ! the main drawing is the bezier curve that connect the moving circle with 8 static circles. Most of the code in this class is to calculate the curve paths (you can play around with hard-code the color for each path) .

The reason, the bezier curve is smooth joined with the circles, is because we use control point stick at the bound of circles:

let (p1, p2) = circleConnectedPoint(circle, other: other, angle: CGMath.degToRad(40))

You can get familiar with CAShapeLayer drawing at here

Why don’t use drawRect: and Core Graphic?
The liquid drawing is achieved by LiquidLoadEffect subclasses composite CAShapeLayer. Both Core Graphic and CAShapeLayer require drawing curves with math, so why don’t use Core Graphic?

There’re some reasons:

  • CAShapeLayer is belongs to CoreAnimation which is backed by GPU. As long as you composite the view by CAShapeLayer and work on it’s predefined property, you gain the speed of GPU drawing.
  • Upload assets to GPU buffer is heavy. Core Graphic calls in drawRect: require drawing to an bitmap buffer in CPU before upload to GPU. In LiquidLoader, CAShapeLayer instances with fillColor and path can be converted to pure GPU calls which is speedy. There’s no need to upload any assets.

Read more


One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.