Gugulethu
Gugulethu

Reputation: 1436

Radial gradient background in Swift

I have been trying to produce a basic radial gradient background, but without success. I managed to get a linear gradient working as shown with the code below, but I have no idea how to make it radial with different colours - like in the image below. Any help would be greatly appreciated. :)

    let gradientLayer: CAGradientLayer = CAGradientLayer()
    gradientLayer.colors = gradientColors
    gradientLayer.locations = gradientLocations ...

enter image description here

enter image description here

Upvotes: 33

Views: 37945

Answers (5)

Fattie
Fattie

Reputation: 12598

Nowadays CAGradientLayer is built-in to iOS.

It's this easy:

For years now you simply do this:

class GlowBall: UIView {
    private lazy var pulse: CAGradientLayer = {
        let l = CAGradientLayer()
        l.type = .radial
        l.colors = [ UIColor.red.cgColor,
            UIColor.yellow.cgColor,
            UIColor.green.cgColor,
            UIColor.blue.cgColor]
        l.locations = [ 0, 0.3, 0.7, 1 ]
        l.startPoint = CGPoint(x: 0.5, y: 0.5)
        l.endPoint = CGPoint(x: 1, y: 1)
        layer.addSublayer(l)
        return l
    }()

    override func layoutSubviews() {
        super.layoutSubviews()
        pulse.frame = bounds
        pulse.cornerRadius = bounds.width / 2.0
    }

}
    

enter image description here

The key lines are:

l.colors = [ UIColor.red.cgColor,
                UIColor.yellow.cgColor,
                UIColor.green.cgColor,
                UIColor.blue.cgColor]
l.locations = [ 0, 0.3, 0.7, 1 ]
    

Note that you can change the "stretch" as you wish ...

l.locations = [ 0, 0.1, 0.2, 1 ]
    
    

enter image description here

Use any colors you like

l.colors = [ UIColor.systemBlue.cgColor,
                    UIColor.systemPink.cgColor,
                    UIColor.systemBlue.cgColor,
                    UIColor.systemPink.cgColor,
                    UIColor.systemBlue.cgColor,
                    UIColor.systemPink.cgColor,
                    UIColor.systemBlue.cgColor,
                    UIColor.systemPink.cgColor]
                l.locations = [ 0,0.1,0.2,0.3,0.4,0.5,0.6,1 ]
    

enter image description here

It's really that easy now.

Very useful trick:

Say you want yellow, with a blue band at 0.6:

l.colors = [ UIColor.yellow.cgColor,
                    UIColor.blue.cgColor,
                    UIColor.yellow.cgColor]
                l.locations = [ 0, 0.6, 1 ]
    

That works fine.

    # yellow...
    # blue...
    # yellow...
    

But usually you do this:

    # yellow...
    # yellow...
    # blue...
    # yellow...
    # yellow...
    

Notice there are TWO of the yellows at each end ...

l.colors = [ UIColor.yellow.cgColor,
 UIColor.yellow.cgColor,
 UIColor.blue.cgColor,
 UIColor.yellow.cgColor,
 UIColor.yellow.cgColor]
    

In this way, you can control "how wide" the blue band is:

In this example: the blue band will be narrow and sharp:

l.locations = [ 0, 0.58, 0.6, 0.68, 1 ]
    

In this example the blue band will be broad and soft:

l.locations = [ 0, 0.5, 0.6, 0.7, 1 ]
    

That is really the secret to how you control gradients, and get the look you want.


How to use ...

Notice this is - very simply - a UIView !!

class GlowBall: UIView { ...

Thus simply

  1. In storyboard, place a UIView where you want

  2. In storyboard, change the class to "GlowBall" instead of UIView

You're done!

Upvotes: 39

Aditya Deshmane
Aditya Deshmane

Reputation: 4722

A little different approach with function which takes parent view, colors, and locations as input. The function returns a subview where the layer was added. This gives the flexibility to hide/show/remove subview.

enter image description here

override func viewDidLoad() {
    super.viewDidLoad()
    //squareView is my parent view I am going to add gradient view to it
    squareView.backgroundColor = UIColor.black
    //Add CG colors
    let colours = [UIColor.red.cgColor,UIColor.green.cgColor,UIColor.clear.cgColor]
    //Add location with same count as colors, these describe contribution in gradient from center 0 to end 1
    let locations:[NSNumber] = [0,0.6,0.8]
    //Use gradientView reference to show/hide, remove/re-add from view
    let gradientView = self.addGradientViewTo(parentView: self.squareView, colors:colours,locations: locations)
}

func addGradientViewTo (parentView:UIView,colors:[CGColor],locations:[NSNumber]) -> UIView  {
    //Create customGradientView with exact dimension of parent, add it with centering with parent
    let customGradientView = UIView()
    customGradientView.backgroundColor = UIColor.clear
    customGradientView.frame = parentView.bounds
    parentView.addSubview(customGradientView)
    customGradientView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor).isActive = true
    customGradientView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor).isActive = true
    parentView.clipsToBounds = true

    //Create layer add it to customGradientView
    let gradientLayer = CAGradientLayer()
    gradientLayer.type = .radial //Circular
    gradientLayer.opacity = 0.8
    gradientLayer.colors = colors
    gradientLayer.locations = locations
    gradientLayer.frame = customGradientView.bounds
    
    //Set start point as center and radius as 1, co-ordinate system maps 0 to 1, 0,0 top left, bottom right 1,1
    gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
    let radius = 1.0
    gradientLayer.endPoint = CGPoint(x: radius, y: radius)
          
    //Add layer at top to make sure its visible
    let layerCount:UInt32 = UInt32(customGradientView.layer.sublayers?.count ?? 0)
    customGradientView.layer.insertSublayer(gradientLayer, at: layerCount)
    customGradientView.layoutIfNeeded()
    
    //Use reference to show/hide add/remove gradient view
    return customGradientView
}

Upvotes: 1

ChikabuZ
ChikabuZ

Reputation: 10185

@IBDesignable class RadialGradientView: UIView {

    @IBInspectable var outsideColor: UIColor = UIColor.red
    @IBInspectable var insideColor: UIColor = UIColor.green

    override func draw(_ rect: CGRect) {
        let colors = [insideColor.cgColor, outsideColor.cgColor] as CFArray
        let endRadius = sqrt(pow(frame.width/2, 2) + pow(frame.height/2, 2))
        let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
        let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
        let context = UIGraphicsGetCurrentContext()

        context?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endRadius, options: CGGradientDrawingOptions.drawsBeforeStartLocation)
    }
}

See my full answer here.

Upvotes: 9

Kurt J
Kurt J

Reputation: 2603

Here is an implementation in Swift 3 if you're just looking for a UIView radial gradient background:

class RadialGradientLayer: CALayer {

    var center: CGPoint {
        return CGPoint(x: bounds.width/2, y: bounds.height/2)
    }

    var radius: CGFloat {
        return (bounds.width + bounds.height)/2
    }

    var colors: [UIColor] = [UIColor.black, UIColor.lightGray] {
        didSet {
            setNeedsDisplay()
        }
    }

    var cgColors: [CGColor] {
        return colors.map({ (color) -> CGColor in
            return color.cgColor
        })
    }

    override init() {
        super.init()
        needsDisplayOnBoundsChange = true
    }

    required init(coder aDecoder: NSCoder) {
        super.init()
    }

    override func draw(in ctx: CGContext) {
        ctx.saveGState()
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let locations: [CGFloat] = [0.0, 1.0]
        guard let gradient = CGGradient(colorsSpace: colorSpace, colors: cgColors as CFArray, locations: locations) else {
            return
        }
        ctx.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: radius, options: CGGradientDrawingOptions(rawValue: 0))
    }

}



class RadialGradientView: UIView {

    private let gradientLayer = RadialGradientLayer()

    var colors: [UIColor] {
        get {
            return gradientLayer.colors
        }
        set {
            gradientLayer.colors = newValue
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        if gradientLayer.superlayer == nil {
            layer.insertSublayer(gradientLayer, at: 0)
        }
        gradientLayer.frame = bounds
    }

}

Upvotes: 23

Zell B.
Zell B.

Reputation: 10286

Have a look at my implementation of RadialGradientLayer, and feel free to modify it

class RadialGradientLayer: CALayer {

   override init(){

        super.init()

        needsDisplayOnBoundsChange = true
    }

     init(center:CGPoint,radius:CGFloat,colors:[CGColor]){

        self.center = center
        self.radius = radius
        self.colors = colors

        super.init()

    }

    required init(coder aDecoder: NSCoder) {

        super.init()

    }

    var center:CGPoint = CGPointMake(50,50)
    var radius:CGFloat = 20
    var colors:[CGColor] = [UIColor(red: 251/255, green: 237/255, blue: 33/255, alpha: 1.0).CGColor , UIColor(red: 251/255, green: 179/255, blue: 108/255, alpha: 1.0).CGColor]

    override func drawInContext(ctx: CGContext!) {

        CGContextSaveGState(ctx)

        var colorSpace = CGColorSpaceCreateDeviceRGB()

        var locations:[CGFloat] = [0.0, 1.0]

        var gradient = CGGradientCreateWithColors(colorSpace, colors, [0.0,1.0])

        var startPoint = CGPointMake(0, self.bounds.height)
        var endPoint = CGPointMake(self.bounds.width, self.bounds.height)

        CGContextDrawRadialGradient(ctx, gradient, center, 0.0, center, radius, 0)

    }

}

In my case I needed it with two colors only and if you need more colors you need to modify location array declared in drawInContext. Also after creating object from this class don't forget to call its setNeedsDisplay() otherwise it wont work. Also sometimes I needed different size gradients so thats why you have to pass radius parameter in initializer and the center point of your gradient

Upvotes: 21

Related Questions