skip to Main Content

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:

enter image description here

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?

2

Answers


  1. Assuming the layer is masking to bounds, you could use the max value between the width and the height as the reference value when creating the gradient.

    Meaning, always create a square gradient, whose size is the max between those values.

    This will make the gradient render at a 45 degree always, as opposed to what you are experiencing. The issue is not that the gradient is not drawing properly, but rather the slope is different because of the size.

    Login or Signup to reply.
  2. 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:

    enter image description here

    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:

    enter image description here

    tgv:

    enter image description here

    and sgv:

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search