3

I am trying to add a CAGradientLayer to a CAShapeLayer, but the gradient is not conforming to the shape of the layer. Instead, the gradient is taking the shape of the UIView itself. I originally attempted this with a UIBezierPath but it did not work either. I am trying to put a gradient into my shape.

  override func drawRect(rect: CGRect) {
        let bounds = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
        let center = self.center

        let path = CAShapeLayer()
        path.bounds = bounds
        path.position = CGPoint(x: center.x, y: center.y)
        //path.backgroundColor = UIColor.redColor().CGColor
        path.cornerRadius = 20
        //self.layer.addSublayer(path)
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = path.bounds
        gradientLayer.colors = [cgColorForRed(209.0, green: 0.0, blue: 0.0),
            cgColorForRed(255.0, green: 102.0, blue: 34.0),
            cgColorForRed(255.0, green: 218.0, blue: 33.0),
            cgColorForRed(51.0, green: 221.0, blue: 0.0),
            cgColorForRed(17.0, green: 51.0, blue: 204.0),
            cgColorForRed(34.0, green: 0.0, blue: 102.0),
            cgColorForRed(51.0, green: 0.0, blue: 68.0)]
        gradientLayer.startPoint = CGPoint(x:0,y:0)
        gradientLayer.endPoint = CGPoint(x:0, y:1)
        path.insertSublayer(gradientLayer, atIndex: 1)
        self.layer.addSublayer(path)


    }

    func cgColorForRed(red: CGFloat, green: CGFloat, blue: CGFloat ) -> AnyObject {
        return UIColor(red: red/255.0, green: green/255.0, blue: blue/255.5, alpha: 1.0).CGColor as AnyObject
    }

Originally I had path defined to be a UIBezierPath of a rectangle with rounded corners which I am looking to fill with the gradient:

        let insetRect = CGRectInset(rect, lineWidth / 2, lineWidth / 2)

        let path = UIBezierPath(roundedRect: insetRect, cornerRadius: 10)

        fillColor.setFill()
        path.fill()

        path.lineWidth = self.lineWidth
        UIColor.blackColor().setStroke()
        path.stroke()

Edit from Larcerax's response:

class Thermometer: UIView {

    //@IBInspectable var fillColor: UIColor = UIColor.greenColor()
    let lineWidth: CGFloat = 2

    override func drawRect(rect: CGRect) {

        let context = UIGraphicsGetCurrentContext()
        let svgid = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [cgColorForRed(209.0, green: 0.0, blue: 0.0),
            cgColorForRed(255.0, green: 102.0, blue: 34.0),
            cgColorForRed(255.0, green: 218.0, blue: 33.0),
            cgColorForRed(51.0, green: 221.0, blue: 0.0),
            cgColorForRed(17.0, green: 51.0, blue: 204.0),
            cgColorForRed(34.0, green: 0.0, blue: 102.0),
            cgColorForRed(51.0, green: 0.0, blue: 68.0)], [0,1])
        CGContextSaveGState(context)
        let insetRect = CGRectInset(rect, lineWidth / 2, lineWidth / 2)

        let path = UIBezierPath(roundedRect: insetRect, cornerRadius: 10)
        path.addClip()
        CGContextDrawLinearGradient(context, svgid, CGPoint(x: 0, y: path.bounds.height),
            CGPoint(x: path.bounds.width, y: path.bounds.height),
            UInt32(kCGGradientDrawsBeforeStartLocation) | UInt32(kCGGradientDrawsAfterEndLocation))
        CGContextRestoreGState(context)
}
wxcoder
  • 665
  • 1
  • 13
  • 32

3 Answers3

5

You can try something like the following set up in order to produce the following shape, this is just an example to show you the setup, but this should do it:

    let context = UIGraphicsGetCurrentContext()

    let gradientColor3 = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
    let gradientColor4 = UIColor(red: 0.000, green: 1.000, blue: 0.000, alpha: 1.000)

    let sVGID_1_2 = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [gradientColor3.CGColor, gradientColor4.CGColor], [0, 1])

    CGContextSaveGState(context)
    CGContextTranslateCTM(context, 198.95, 199.4)
    CGContextRotateCTM(context, -30 * CGFloat(M_PI) / 180)

    var polygonPath = UIBezierPath()
    polygonPath.moveToPoint(CGPointMake(0, -145.95))
    polygonPath.addLineToPoint(CGPointMake(126.4, -72.98))
    polygonPath.addLineToPoint(CGPointMake(126.4, 72.97))
    polygonPath.addLineToPoint(CGPointMake(0, 145.95))
    polygonPath.addLineToPoint(CGPointMake(-126.4, 72.98))
    polygonPath.addLineToPoint(CGPointMake(-126.4, -72.97))
    polygonPath.closePath()
    CGContextSaveGState(context)
    polygonPath.addClip()
    CGContextDrawLinearGradient(context, sVGID_1_2,
        CGPointMake(-126.4, -72.97),
        CGPointMake(126.41, 72.99),
        UInt32(kCGGradientDrawsBeforeStartLocation) | UInt32(kCGGradientDrawsAfterEndLocation))
    CGContextRestoreGState(context)

to produce this:

enter image description here

If this doesn't work, then tell me, I will try to fix it up to make it work.

Also, here's just a simple rectangle:

let context = UIGraphicsGetCurrentContext()

let gradientColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
let gradientColor2 = UIColor(red: 0.988, green: 0.933, blue: 0.129, alpha: 1.000)
let gradientColor3 = UIColor(red: 1.000, green: 0.000, blue: 1.000, alpha: 1.000)

let sVGID_1_3 = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [gradientColor.CGColor, gradientColor2.CGColor, gradientColor3.CGColor], [0, 0.43, 1])

let rectanglePath = UIBezierPath(rect: CGRectMake(68, 28, 78.4, 78.4))
CGContextSaveGState(context)
rectanglePath.addClip()
CGContextDrawLinearGradient(context, sVGID_1_3,
    CGPointMake(68, 67.19),
    CGPointMake(146.38, 67.19),
    UInt32(kCGGradientDrawsBeforeStartLocation) | UInt32(kCGGradientDrawsAfterEndLocation))
CGContextRestoreGState(context)

enter image description here

here's a function to try out, you'll have to mess around with the colors, but you can input a frame:

class func drawStuff(#frame: CGRect) {
        let context = UIGraphicsGetCurrentContext()

        let gradientColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
        let gradientColor2 = UIColor(red: 0.988, green: 0.933, blue: 0.129, alpha: 1.000)
        let gradientColor3 = UIColor(red: 1.000, green: 0.000, blue: 1.000, alpha: 1.000)

        let sVGID_1_4 = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [gradientColor.CGColor, gradientColor2.CGColor, gradientColor3.CGColor], [0, 0.43, 1])

        let rectangleRect = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height)
        let rectanglePath = UIBezierPath(rect: rectangleRect)
        CGContextSaveGState(context)
        rectanglePath.addClip()
        CGContextDrawLinearGradient(context, sVGID_1_4, CGPointMake(rectangleRect.minX, rectangleRect.midY),CGPointMake(rectangleRect.maxX, rectangleRect.midY), 0)
        CGContextRestoreGState(context)
    }
Larry Pickles
  • 4,615
  • 2
  • 19
  • 36
  • This works great although it exhibits weird behavior when I launch the simulator and click on shape it changes color and sometimes the gradient disappears leaving the outline. I don't have segues at the moment. Maybe it's my auto layout constraints? – wxcoder Aug 20 '15 at 05:03
  • that's weird, let me see if I can test it – Larry Pickles Aug 20 '15 at 05:05
  • crap, well I'm calling to "CGContextRestoreGState(context)" two times, remove the second one, not sure if this matters – Larry Pickles Aug 20 '15 at 05:13
  • so, try the rectangle shape alone, it's very much simplified, and yes, your constraints could be causing and issue – Larry Pickles Aug 20 '15 at 05:15
  • I assumed you didn't mean to do that. – wxcoder Aug 20 '15 at 05:15
  • I am using the rectangle shape . – wxcoder Aug 20 '15 at 05:16
  • I don't know if it matters but this UIView is within a table cell view. I'll edit by OP with the code I have based on your example. – wxcoder Aug 20 '15 at 05:21
  • it shouldn't matter, but I'm making it a public function for you to try out, one sec – Larry Pickles Aug 20 '15 at 05:22
  • When I tried to run the simulator on an iPhone 6+ it gave me this: 01111111Assertion failed: (CGFloatIsValid(x) && CGFloatIsValid(y)), function void CGPathMoveToPoint(CGMutablePathRef, const CGAffineTransform *, CGFloat, CGFloat), file Paths/CGPath.cc, line 254. This is in the path definition. – wxcoder Aug 20 '15 at 05:30
  • one sec, let me check this out – Larry Pickles Aug 20 '15 at 05:30
  • Yeah, I can't recreate it, I've tried like everything possible – Larry Pickles Aug 20 '15 at 05:55
  • Even in a table view cell? I can't see why my auto layout constraints would be an issue, there's not errors there... Your answer is technically correct for what I asked it's just the behavior in the simulator is weird. Responding to touches- changing the gradient, removing it at times, or even not showing up at all in some of the cells. – wxcoder Aug 20 '15 at 05:58
  • Yeah, that's totally weird, but I'm looking at responses to the error you are getting and it looks like it has to do with X/Y values that are either ZERO or integers when they should be double, I guess – Larry Pickles Aug 20 '15 at 05:59
  • and in fact, are you positive that you BOUNDS are NON-ZERO values – Larry Pickles Aug 20 '15 at 06:00
  • I wonder if "path.bounds.width" or "path.bounds.height" is returning a zero value for some reason, of if the path's bounds are undefined or something – Larry Pickles Aug 20 '15 at 06:01
  • I did a print of path.bounds.width and path.bounds.height and they are non-zero. The values aren't the same for all of them but i'm assuming that's because it's adjusting to the constraints. The constraints on my uiview are bottom space to : Superview Equals: 13.5, Top Space Equals: 1, height equals 31, Trailing Space to: 90 Equals 8, Leading Space to: 70 Equals 8. I went ahead an accepted your answer although i'm still having a problem on my end. – wxcoder Aug 20 '15 at 06:07
  • yeah, it shouldn't matter if they aren't the same since the tableview cells are giong to be redrawn for each cell, unless there's something super weird happening if/when you dequeue a cell, if you are dequeueing cells, but that still wouldn't make much sense to me, I wonder if setting it to "frame" instead of "bounds" would solve this, but probably not – Larry Pickles Aug 20 '15 at 06:11
  • I think I figured out my issue. It looks like it had something to do with the way that I was defining the gradient for the colorbar causing that chaotic behavior. – wxcoder Aug 20 '15 at 06:30
1

Or you can just use this written by myself :) https://github.com/BilalReffas/SwiftyGradient

BilalReffas
  • 8,132
  • 4
  • 50
  • 71
1

Swift 4 version of the first shape from @Loxx answer above which produces this output:

@Loxx shape#1.

private func drawShape() {
    guard let context = UIGraphicsGetCurrentContext() else {
        print("No context, handle me")
        return
    }

    let gradientColor3 = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
    let gradientColor4 = UIColor(red: 0.000, green: 1.000, blue: 0.000, alpha: 1.000)

    guard let sVGID_1_2 = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [gradientColor3.cgColor, gradientColor4.cgColor] as CFArray, locations: [0, 1]) else {
        print("No gradient, handle me")
        context.restoreGState()
        return
    }

    context.saveGState()
    context.translateBy(x: 198.95, y: 199.4)
    context.rotate(by: -30 * CGFloat(Double.pi) / 180)

    let polygonPath = UIBezierPath()
    polygonPath.move(to: CGPoint(x: 0, y: -145.95))
    polygonPath.addLine(to: CGPoint(x: 126.4, y: -72.98))
    polygonPath.addLine(to: CGPoint(x: 126.4, y: 72.97))
    polygonPath.addLine(to: CGPoint(x: 0, y: 145.95))
    polygonPath.addLine(to: CGPoint(x: -126.4, y: 72.98))
    polygonPath.addLine(to: CGPoint(x: -126.4, y: -72.97))
    polygonPath.close()
    context.saveGState()
    polygonPath.addClip()
    context.drawLinearGradient(sVGID_1_2,
                               start: CGPoint(x: -126.4, y: -72.97),
                               end: CGPoint(x: 126.41, y: 72.99),
                               options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
    context.restoreGState()
}

I'm posting it because basically every line has changed in conversion to Swift 4 and it took a while to convert it just to check if it works.

Nat
  • 12,032
  • 9
  • 56
  • 103