Welkin's Secret Garden

Implementation of Sticky Ball

Sometimes, We meet this sticky ball in the apps that we use in our daily life.

It is widely used. Just for QQ, the unread numbers and the pull-to-refresh are implemented with it. So why not start to implement it.

We need a knowledge point to implement it, that is, Bezier Curve .

You can find its principle on google. In a brief, input the initial, ending and control point, and it will generate a smooth line connecting initial and ending point in proportion.

Let’s Analyse this Animation。

  • Consist of two balls.
  • One of them is draggable.
  • The fixed ball’s radius decreases when dragging.
  • The lines between balls changes when dragging.

Get to the point

Things like generation of balls and adding gesture recognizer is skipped and I will tell the aporia, that is, the generation of the lines connecting the balls. See the graph bellow.

Because we need initial and ending point so that we can get the Bezier Curve. We first work out the positions of two balls.

Let’s assume the center of the fixed ball is O and the draggable one is P. Then we need to connect A to D and B to C with Bezier Curve. As for the control point of A to D, I draw a tangent line at A, which is parallel to OP, and then draw the midperpendicular of OP so as to obtain their intersection point M which is the control point of A to D. Similarly, I get N as B to C’s control point.

Thus, with A, D, B, C, M, N and the definition of an angle α, we can now create Bezier Curve with our SDK.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func updateLines() -> Void {
let o = CGPoint(x: bottomBubble.center.x, y: bottomBubble.center.y)
let p = CGPoint(x: topBubble.center.x, y: topBubble.center.y)
let r1 = bottomBubble.frame.size.width / 2
let r2 = topBubble.frame.size.width / 2
let cos = (p.y - o.y) / (sqrt(pow(p.x - o.x, 2) + pow(p.y - o.y, 2)))
let sin = (p.x - o.x) / (sqrt(pow(p.x - o.x, 2) + pow(p.y - o.y, 2)))

let x1 = r1 * cos
let y1 = r1 * sin
let x2 = r2 * cos
let y2 = r2 * sin

let q = CGPoint(x: (o.x + p.x) / 2, y: (o.y + p.y) / 2)
let a = CGPoint(x: o.x - x1, y: o.y + y1)
let b = CGPoint(x: o.x + x1, y: o.y - y1)
let c = CGPoint(x: p.x + x2, y: p.y - y2)
let d = CGPoint(x: p.x - x2, y: p.y + y2)
let n = CGPoint(x: q.x + x1, y: q.y - y1)
let m = CGPoint(x: q.x - x1, y: q.y + y1)

let bPath = UIBezierPath()
bPath.move(to: a)
bPath.addQuadCurve(to: d, controlPoint: m)
bPath.addLine(to: c)
bPath.addQuadCurve(to: b, controlPoint: n)
bPath.addLine(to: a)

shapeLayer.path = bPath.cgPath
}

What we need is to invoke updateLines() constantly when draging to generate the curve in real time.