An angled gradient can be achieved using some basic trigonometry. You can achieve it by subclassing UIView, as I describe in my blog post on the subject.
First define some variables:-
// The end point of the gradient when drawn in the layer’s coordinate space. Animatable.
var endPoint: CGPoint
// The start point of the gradient when drawn in the layer’s coordinate space. Animatable.
var startPoint: CGPoint
// the gradient angle, in degrees anticlockwise from 0 (east/right)
@IBInspectable var angle: CGFloat = 270
The core function, below, gets the start and end points in unit space.
// create vector pointing in direction of angle
private func gradientPointsForAngle(_ angle: CGFloat) -> (CGPoint, CGPoint) {
// get vector start and end points
let end = pointForAngle(angle)
let start = oppositePoint(end)
// convert to gradient space
let p0 = transformToGradientSpace(start)
let p1 = transformToGradientSpace(end)
return (p0, p1)
}
This simply takes the angle that the user specified and uses it to create a vector pointing in that direction. The angle specifies the rotation of the vector from 0 degrees, which by convention points east in Core Animation, and increases anti-clockwise (counter-clockwise).
The rest of the relevant code is below, and is concerned with the fact that the resulting point is on the unit circle. The points we need, however, are in a unit square: the vector is extrapolated to the unit square.
private func pointForAngle(_ angle: CGFloat) -> CGPoint {
// convert degrees to radians
let radians = angle * .pi / 180.0
var x = cos(radians)
var y = sin(radians)
// (x,y) is in terms unit circle. Extrapolate to unit square to get full vector length
if (fabs(x) > fabs(y)) {
// extrapolate x to unit length
x = x > 0 ? 1 : -1
y = x * tan(radians)
} else {
// extrapolate y to unit length
y = y > 0 ? 1 : -1
x = y / tan(radians)
}
return CGPoint(x: x, y: y)
}
private func oppositePoint(_ point: CGPoint) -> CGPoint {
return CGPoint(x: -point.x, y: -point.y)
}
private func transformToGradientSpace(_ point: CGPoint) -> CGPoint {
// input point is in signed unit space: (-1,-1) to (1,1)
// convert to gradient space: (0,0) to (1,1), with flipped Y axis
return CGPoint(x: (point.x + 1) * 0.5, y: 1.0 - (point.y + 1) * 0.5)
}
And ultimately everything must be called from an update function:-
private func updateGradient() {
if let gradient = self.gradient {
let (start, end) = gradientPointsForAngle(self.angle)
gradient.startPoint = start
gradient.endPoint = end
gradient.frame = self.bounds
}
}
For the full implementation please see my blog post.