Mobile Development 15 min read

How to Build a Gradient Border Voting Button with Custom Animations on iOS

This article walks through the design and implementation of a voting component featuring a transparent gradient border and gradient‑filled irregular button, covering three solution options, detailed path calculations, CoreGraphics and CoreAnimation techniques, and complete Swift code for drawing and animating the UI.

DaTaobao Tech
DaTaobao Tech
DaTaobao Tech
How to Build a Gradient Border Voting Button with Custom Animations on iOS

Requirement

The component must display a voting option as an irregular button whose border is a transparent gradient and whose background is a gradient fill, with a smooth animation when the user taps it.

Solution Comparison

GIF – Easy to use but consumes large file size and offers little customization.

Lottie – More flexible; requires a designer to provide an animation JSON file.

Code drawing – Most flexible and lightweight, but the implementation is more complex.

Considering package size, collaboration cost, and animation complexity, the code‑drawing approach was chosen.

Path Analysis

The outer black line represents the visible area of the GradientLayer mask (the ShapeLayer path for the fill).

The inner yellow line is the visible area of the border GradientLayer mask (the ShapeLayer path for the stroke).

The blue line indicates the radius; the outer radius is larger than the inner radius by borderWidth/2.

Because a stroke is centered on its path, the border must be offset by borderWidth/2 to keep it fully inside the desired area.

Both black and yellow lines share the same corner center point.

Border width is 12 pt, left circle radius is half the height (36 pt), corner radii on the right are 8 pt, resulting in left radius 30 pt and right radius 2 pt.

From the diagram we need to compute three key points for the path:

left  = (height/2, height/2)
rightTop = (width - radius / tan(angle/2), radius)
rightBottom = (width - height / tan(angle) - radius * tan(angle/2), height - radius)

Key Knowledge Points

CALayer mask – A mask layer defines the visible region of its parent layer; the mask’s color is irrelevant, only its shape matters.

anchorPoint – Determines the reference point for all layer transformations. Default is (0.5, 0.5). Setting it to (0, 0) or (1, 0) makes the left or right option shrink toward its respective edge.

stroke vs. fill – stroke draws a line centered on the path (requires offset), while fill fills the interior bounded by the path.

CoreGraphics – The low‑level graphics library providing CGPoint, CGRect, CGSize, etc.

QuartzCore / CoreAnimation – Provides layer‑based animation APIs such as CAKeyframeAnimation and CAMediaTimingFunction. Animations are applied to CALayer, not UIView.

UIBezierPath – UIKit wrapper around CGPath for creating circles, arcs, rectangles, and custom shapes.

Drawing the Path

private func drawBezierPath(rect: CGRect, smallAngle: CGFloat, shape: Bool) -> UIBezierPath {
    let bezierPath = UIBezierPath()
    let circleRadius = rect.height / 2
    let leftTop = state == .left ? circleRadius : bigRadius
    let rightTop = state == .left ? smallRadius : circleRadius
    let rightBottom = state == .left ? bigRadius : circleRadius
    let leftBottom = state == .left ? circleRadius : smallRadius
    let radiusOffset = shape ? 0 : borderWidth / 2
    // calculate points based on state …
    // add arcs and lines
    return bezierPath
}

Animating the Component

The animation consists of three stages: initial shape, narrowed intermediate shape, and final shape. Each stage updates the path and bounds of the shape and border layers.

public func startAnimate() {
    let timingFunctions = [CAMediaTimingFunction(name: .linear), CAMediaTimingFunction(name: .linear)]
    let shapeKeyFrameAnimation = CAKeyframeAnimation(keyPath: "path")
    shapeKeyFrameAnimation.duration = 2
    shapeKeyFrameAnimation.fillMode = .forwards
    shapeKeyFrameAnimation.isRemovedOnCompletion = false
    shapeKeyFrameAnimation.values = [shapePath, middleShapePath, endShapePath]
    shapeKeyFrameAnimation.timingFunctions = timingFunctions
    shaperLayer.removeAllAnimations()
    shaperLayer.add(shapeKeyFrameAnimation, forKey: "shapeAnimation")

    let borderKeyFrameAnimation = CAKeyframeAnimation(keyPath: "path")
    borderKeyFrameAnimation.duration = 2
    borderKeyFrameAnimation.fillMode = .forwards
    borderKeyFrameAnimation.isRemovedOnCompletion = false
    borderKeyFrameAnimation.values = [borderPath, middleBorderPath, endBorderPath]
    borderKeyFrameAnimation.timingFunctions = timingFunctions
    borderLayer.removeAllAnimations()
    borderLayer.add(borderKeyFrameAnimation, forKey: "borderAnimation")

    let shapeGradientKeyFrameAnimation = CAKeyframeAnimation(keyPath: "bounds")
    shapeGradientKeyFrameAnimation.duration = 2
    shapeGradientKeyFrameAnimation.fillMode = .forwards
    shapeGradientKeyFrameAnimation.isRemovedOnCompletion = false
    shapeGradientKeyFrameAnimation.values = [startBounds, middleBounds, endBounds]
    shapeGradientKeyFrameAnimation.timingFunctions = timingFunctions
    shapeGradientLayer.removeAllAnimations()
    shapeGradientLayer.add(shapeGradientKeyFrameAnimation, forKey: "shapeGradientAnimation")
    borderGradientLayer.removeAllAnimations()
    borderGradientLayer.add(shapeGradientKeyFrameAnimation, forKey: "shapeGradientAnimation")
}

Conclusion

The problem reduces to precise geometric calculations; drawing helper lines greatly improves development efficiency. The demo code shows a functional prototype, while a production version would need to handle edge cases such as 100 %/0 % voting ratios, extreme width ratios, text layout, and tap handling.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

iOSMobileDevelopmentCoreAnimationCoreGraphicsGradientBorderUIBezierPath
DaTaobao Tech
Written by

DaTaobao Tech

Official account of DaTaobao Technology

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.