I can demonstrate the issue with this simple example.
I have a view which needs diagonal gradient which should start with one color at top left corner and change to another color at the bottom right corner. The following code shows my attempt:
import UIKit
class GradientView: UIView {
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
context.clip(to: rect)
let topColor = UIColor.red
let bottomColor = UIColor.green
var tr = CGFloat(0), tg = CGFloat(0), tb = CGFloat(0), ta = CGFloat(0), br = CGFloat(0), bg = CGFloat(0), bb = CGFloat(0), ba = CGFloat(0)//top and bottom component variables
topColor.getRed(&tr, green: &tg, blue: &tb, alpha: &ta)
bottomColor.getRed(&br, green: &bg, blue: &bb, alpha: &ba)
let gradient = CGGradient(colorSpace: CGColorSpaceCreateDeviceRGB(), colorComponents: [tr,tg,tb,ta,br,bg,bb,ba], locations: [0.0, 1.0], count: 2)
context.drawLinearGradient(gradient!, start: CGPoint(x: 0, y: 0), end: CGPoint(x: rect.size.width, y: rect.size.height), options: CGGradientDrawingOptions.drawsAfterEndLocation)
context.restoreGState()
}
}
Here's how it looks:
As you can see, in the bottom square, when height is same as width, it works fine. However, in the top rectangle, when the width is longer than height, it doesn't work correctly as the top right corner doesn't have any red whatsoever and the bottom left corner doesn't have any green whatsoever.
How can I fix this?
In order to get the desired result you need to calculate start and end points that connect a line that is perpendicular to the diagonal line through the rectangle. See the following diagram:
The following update to your code calculates the values for dx and dy which can be used to get the start and end points of the gradient.
class GradientView: UIView {
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
let topColor = UIColor.red
let bottomColor = UIColor.green
let ang = atan(bounds.height / bounds.width)
let len = cos(ang) * bounds.height
let dx = sin(ang) * len
let dy = cos(ang) * len
// Create the gradient using the two colors
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [topColor.cgColor, bottomColor.cgColor] as CFArray, locations: nil)!
// Draw the gradient basing the start and end points off of the
// center of the rectangle.
let start = CGPoint(x: bounds.midX - dx, y: bounds.midY - dy)
let end = CGPoint(x: bounds.midX + dx, y: bounds.midY + dy)
context.drawLinearGradient(gradient, start: start, end: end, options: [ .drawsBeforeStartLocation, .drawsAfterEndLocation ])
}
}
The following sample views (wide, tall, and square) demonstrate the desired output for all three cases:
let wgv = GradientView(frame: CGRect(x: 0, y: 0, width: 400, height: 100))
let tgv = GradientView(frame: CGRect(x: 0, y: 0, width: 100, height: 400))
let sgv = GradientView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
The results are:
wgv:
tgv:
and sgv: