Right way to reproduce sketch gradient with CAGradientLayer - ios

I would like to create a gradient layer with four given colours. I have a sketch file for the gradients. I have the coordinates for the colour stops. I calculated the normalised coordinates for the start and the end point for the CAGradientLayer. I also calculated the location where the colours change each other. Still the layer does not match with the sketch image. Can you help me what could be wrong? Usage of the exported image is not an option because the layer has to change with animation to other gradient locations.
let imageWidth: CGFloat = 375.0
let imageHeihgt: CGFloat = 293.0
class ViewController: UIViewController {
let homeColors: [UIColor] = [UIColor(red: 62/255, green: 131/255, blue: 255/255, alpha: 1.0),
UIColor(red: 99/255, green: 22/255, blue: 203/255, alpha: 1.0),
UIColor(red: 122/255, green: 5/255, blue: 239/255, alpha: 1.0),
UIColor(red: 122/255, green: 5/255, blue: 240/255, alpha: 1.0)]
let startPoint: CGPoint = CGPoint(x: -0.225/imageWidth*UIScreen.main.bounds.width, y: -0.582*imageHeihgt/UIScreen.main.bounds.height)
let endPoint: CGPoint = CGPoint(x: 1.088/imageWidth*UIScreen.main.bounds.width, y: 1.01*imageHeihgt/UIScreen.main.bounds.height)
let locations: [NSNumber] = [0.0, 0.734, 0.962, 1.0]
var subview: UIView!
override func viewDidLoad() {
super.viewDidLoad()
subview = UIView(frame: CGRect(x: 0, y: 20, width: imageWidth, height: imageHeihgt))
view.addSubview(subview)
view.sendSubview(toBack: subview)
addGradientLayer()
}
func addGradientLayer() {
let gradient = CAGradientLayer()
gradient.frame = subview.bounds
gradient.colors = homeColors.map { $0.cgColor }
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.locations = locations
subview.layer.insertSublayer(gradient, at: 0)
}
}
I have attached some screenshots to make the difference clear. It is not a big difference but still.

Sketch renders graphics differently than iOS. Even if you match the colors and locations perfectly, the end result will still look different.
If you want an exact match to the Sketch file, you'll need to make some optical adjustments to the colors to account for the differences. Your two images are very close, so with some minor color adjustments, it should look a lot closer.
Try making your light blue (top left) and light purple (bottom right) less vibrant. I don't have access to your Sketch file, so I can't suggest new colors to get an exact match, but it shouldn't be too hard, just a little trial and error.

I wrote a script convert Sketch gradient layer to CALayer. Insert this layer by view.layer.insertSublayer(gradientLayer, at: 0).
Link: Convert Sketch Gradient To Swift Code
Select a layer which contains one gradient fill, then press control + shift + K, paste code below and run. you will see:
And on iPhone it looks like:
console.log('Select Layer Then Run Script.');
var sketch = require('sketch');
var UI = require('sketch/ui');
var document = sketch.getSelectedDocument();
var selectedLayers = document.selectedLayers;
var selectedCount = selectedLayers.length;
function hexToRGBA(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
a: parseInt(result[4], 16)
} : null;
}
let precesion = 10000
function parseGradient(layerGradient) {
let colorItems = ""
let locationArray = ""
layerGradient.stops.forEach(elm => {
let rgbaColor = hexToRGBA(elm.color)
colorItems += `UIColor(red: ${rgbaColor.r}/255.0, green: ${rgbaColor.g}/255.0, blue: ${rgbaColor.b}/255.0, alpha: ${rgbaColor.a}/255.0), `
locationArray += `${Math.round(elm.position*precesion)/precesion}, `
});
var codeTemplate = `
import Foundation
import UIKit
class LinearGradientLayer: CALayer {
required override init() {
super.init()
needsDisplayOnBoundsChange = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
required override init(layer: Any) {
super.init(layer: layer)
}
let colors = [${colorItems}].map(\{ \$0.cgColor \})
override func draw(in ctx: CGContext) {
ctx.saveGState()
let colorSpace = CGColorSpaceCreateDeviceRGB()
let locations: [CGFloat] = [${locationArray}]
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations)!
ctx.drawLinearGradient(gradient, start: CGPoint(x: ${Math.round(layerGradient.from.x*precesion)/precesion}*bounds.width, y: ${Math.round(layerGradient.from.y*precesion)/precesion}*bounds.height), end: CGPoint(x: ${Math.round(layerGradient.to.x*precesion)/precesion}*bounds.width, y: ${Math.round(layerGradient.to.y*precesion)/precesion}*bounds.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
}
}
`;
return codeTemplate
}
if (selectedCount === 0) {
UI.alert('No layers are selected', 'Select one gradient filled layer and rerun.')
} else {
selectedLayers.forEach(function (layer, i) {
console.log((i + 1) + '. ' + layer.name);
let layerGradient = layer.style.fills[0].gradient;
console.log(layerGradient)
UI.getInputFromUser(
"Convert Result",
{
initialValue: parseGradient(layerGradient),
numberOfLines: 12
},
(err, value) => {
if (err) {
// most likely the user canceled the input
return;
}
}
);
});
}

Related

Use radial gradient as an extention for UIView

My goal is to make radial gradient extension for UIView. Here is my code:
extension UIView {
func drawRadialGradient() {
let colors = Colors.gradientColors as CFArray
let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
guard let gradientValue = gradient else{ return }
let endRadius: CGFloat? = max(frame.width, frame.height) / 2
guard let endRadiusValue = endRadius else{ return }
let bottomcenterCoordinates = CGPoint(x: frame.width / 2, y: frame.height)
let getCurrentContext = UIGraphicsGetCurrentContext()
guard let currentContext = getCurrentContext else{ return }
currentContext.drawRadialGradient(gradientValue, startCenter: bottomcenterCoordinates, startRadius: 0.0, endCenter: bottomcenterCoordinates, endRadius: endRadiusValue, options: CGGradientDrawingOptions.drawsAfterEndLocation)
let radialGradientLayer = CALayer(layer: currentContext)
radialGradientLayer.frame = bounds
radialGradientLayer.masksToBounds = true
self.layer.insertSublayer(radialGradientLayer, at: 1)
}
}
When I call this function in viewDidLoad() or viewWillAppear() the compiler contains no mistakes and no warnings, the function just does not work out. i call it as following:
override func viewDidLoad() {
super.viewDidLoad()
view.drawRadialGradient()
}
For example, I have created an extension function for drawing a Linear Gradient on the UIView and it works, I call it the same way as radial gradient function:
func drawLinearGradient() {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.frame
gradientLayer.colors = Colors.gradientColors
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.95)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.05)
self.layer.insertSublayer(gradientLayer, at: 0)
}
For colors I have created a structure:
struct Colors {
static let firstColor = colorPicker(red: 70, green: 183, blue: 0)
static let secondColor = colorPicker(red: 0, green: 170, blue: 116)
static let thirdColor = colorPicker(red: 20, green: 0, blue: 204)
static let gradientColors = [firstColor.cgColor, secondColor.cgColor, thirdColor.cgColor]
static func colorPicker(red: CGFloat, green: CGFloat, blue: CGFloat) -> UIColor {
let color = UIColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: 1.0)
return color
}
}
Please, give me a piece of advice on how to realize it as an extension.
One main thing I can see in your code, is that you try to do the drawing in viewDidLoad. Don't do that, on top of other problems, the frame size is not properly set yet at that moment. If you want the UIView to do the drawing, then derive a class from UIView, and do the drawing in the draw method of that class.
If you want the radialGradientLayer CALayer that you created to do the drawing (it currently is just empty), then derive a subclass from CALayer, and implement its drawInContext method.

How to add tap gesture on each segment of a Piechart using UIGraphicsGetCurrentContext arc

I am trying to create a Piechart using UIGraphicsGetCurrentContext for drawing, I want to add tap gesture on the piechart to recognize if the tap point inside each segment. Please find the code below for drawing Piechart and the ViewController where the clas . is used,
PiechartView.swift
import UIKit
private extension CGFloat {
/// Formats the CGFloat to a maximum of 1 decimal place.
var formattedToOneDecimalPlace : String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 1
return formatter.string(from: NSNumber(value: self.native)) ?? "\(self)"
}
}
/// Defines a segment of the pie chart
struct Segment {
/// The color of the segment
var color : UIColor
/// The name of the segment
var name : String
/// The value of the segment
var value : CGFloat
}
class PieChartView: UIView {
/// An array of structs representing the segments of the pie chart
var segments = [Segment]() {
didSet { setNeedsDisplay() } // re-draw view when the values get set
}
/// Defines whether the segment labels should be shown when drawing the pie chart
var showSegmentLabels = true {
didSet { setNeedsDisplay() }
}
/// Defines whether the segment labels will show the value of the segment in brackets
var showSegmentValueInLabel = false {
didSet { setNeedsDisplay() }
}
/// The font to be used on the segment labels
var segmentLabelFont = UIFont.systemFont(ofSize: 20) {
didSet {
textAttributes[NSFontAttributeName] = segmentLabelFont
setNeedsDisplay()
}
}
private let paragraphStyle : NSParagraphStyle = {
var p = NSMutableParagraphStyle()
p.alignment = .center
return p.copy() as! NSParagraphStyle
}()
private lazy var textAttributes : [String : Any] = {
return [NSParagraphStyleAttributeName : self.paragraphStyle, NSFontAttributeName : self.segmentLabelFont]
}()
override init(frame: CGRect) {
super.init(frame: frame)
isOpaque = false // when overriding drawRect, you must specify this to maintain transparency.
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
// get current context
let ctx = UIGraphicsGetCurrentContext()
// radius is the half the frame's width or height (whichever is smallest)
let radius = min(frame.width, frame.height) * 0.5
// center of the view
let viewCenter = CGPoint(x: bounds.size.width * 0.5, y: bounds.size.height * 0.5)
// enumerate the total value of the segments by using reduce to sum them
let valueCount = segments.reduce(0, {$0 + $1.value})
// the starting angle is -90 degrees (top of the circle, as the context is flipped). By default, 0 is the right hand side of the circle, with the positive angle being in an anti-clockwise direction (same as a unit circle in maths).
var startAngle = -CGFloat.pi * 0.5
// loop through the values array
for segment in segments {
// set fill color to the segment color
ctx?.setFillColor(segment.color.cgColor)
// update the end angle of the segment
let endAngle = startAngle + .pi * 2 * (segment.value / valueCount)
// move to the center of the pie chart
ctx?.move(to: viewCenter)
// add arc from the center for each segment (anticlockwise is specified for the arc, but as the view flips the context, it will produce a clockwise arc)
ctx?.addArc(center: viewCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
// fill segment
ctx?.fillPath()
if showSegmentLabels { // do text rendering
// get the angle midpoint
let halfAngle = startAngle + (endAngle - startAngle) * 0.5;
// the ratio of how far away from the center of the pie chart the text will appear
let textPositionValue : CGFloat = 0.67
// get the 'center' of the segment. It's slightly biased to the outer edge, as it's wider.
let segmentCenter = CGPoint(x: viewCenter.x + radius * textPositionValue * cos(halfAngle), y: viewCenter.y + radius * textPositionValue * sin(halfAngle))
// text to render – the segment value is formatted to 1dp if needed to be displayed.
let textToRender = showSegmentValueInLabel ? "\(segment.name) (\(segment.value.formattedToOneDecimalPlace))" : segment.name
// get the color components of the segement color
guard let colorComponents = segment.color.cgColor.components else { return }
// get the average brightness of the color
let averageRGB = (colorComponents[0] + colorComponents[1] + colorComponents[2]) / 3
// if too light, use black. If too dark, use white
textAttributes[NSForegroundColorAttributeName] = (averageRGB > 0.7) ? UIColor.black : UIColor.white
// the bounds that the text will occupy
var renderRect = CGRect(origin: .zero, size: textToRender.size(attributes: textAttributes))
// center the origin of the rect
renderRect.origin = CGPoint(x: segmentCenter.x - renderRect.size.width * 0.5, y: segmentCenter.y - renderRect.size.height * 0.5)
// draw text in the rect, with the given attributes
textToRender.draw(in: renderRect, withAttributes: textAttributes)
}
// update starting angle of the next segment to the ending angle of this segment
startAngle = endAngle
}
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
let pieChartView = PieChartView()
override func viewDidLoad() {
super.viewDidLoad()
pieChartView.frame = CGRect(x: 0, y: 40, width: UIScreen.main.bounds.size.width, height: 400)
pieChartView.segments = [
Segment(color: UIColor(red: 1.0, green: 31.0/255.0, blue: 73.0/255.0, alpha: 1.0), name:"Red", value: 57.56),
Segment(color: UIColor(red:1.0, green: 138.0/255.0, blue: 0.0, alpha: 1.0), name: "Orange", value: 30),
Segment(color: UIColor(red: 122.0/255.0, green: 108.0/255.0, blue: 1.0, alpha: 1.0), name: "Purple", value: 27),
Segment(color: UIColor(red: 0.0, green: 222.0/255.0, blue: 1.0, alpha: 1.0), name: "Light Blue", value: 40),
Segment(color: UIColor(red: 100.0/255.0, green: 241.0/255.0, blue: 183.0/255.0, alpha: 1.0), name: "Green", value: 25),
Segment(color: UIColor(red: 0.0, green: 100.0/255.0, blue: 1.0, alpha: 1.0), name: "Blue", value: 38)
]
pieChartView.segmentLabelFont = UIFont.systemFont(ofSize: 18)
pieChartView.showSegmentValueInLabel = true
view.addSubview(pieChartView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Please let me know how do I add tap gesture on each segment. Thanks in advance.
Found a solution at https://github.com/dilipajm/piechart
Though it's in Objective C but we can try to do the same with swift too.

Filling Undefined forms with Gradient color SWIFT

I am new to programming and I have no idea how I can fill a undefined geometrical form with a gradient color...
I managed to do with a simple color like that:
func fillRegion(pixelX: Int, pixelY: Int, withColor color: UIColor) {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
var newColor = (UInt32)(alpha*255)<<24 | (UInt32)(red*255)<<16 | (UInt32)(green*255)<<8 | (UInt32)(blue*255)<<0
let pixelColor = regionsData.advanced(by: (pixelY * imageHeight) + pixelX).pointee
if pixelColor == blackColor { return }
var pointerRegionsData: UnsafeMutablePointer<UInt32> = regionsData
var pointerImageData: UnsafeMutablePointer<UInt32> = imageData
var pixelsChanged = false
for i in 0...(imageHeight * imageHeight - 1) {
if pointerRegionsData.pointee == pixelColor {
pointerImageData = imageData.advanced(by: i)
if pointerImageData.pointee != newColor {
// newColor = newColor + 1
pointerImageData.pointee = newColor
pixelsChanged = true
}
}
pointerRegionsData = pointerRegionsData.successor()
}
if pixelsChanged {
self.image = UIImage(cgImage: imageContext.makeImage()!)
DispatchQueue.main.async {
CATransaction.setDisableActions(true)
self.layer.contents = self.image.cgImage
self.onImageDraw?(self.image)
}
self.playTapSound()
}
}
Pixel by pixel it fill the color (ignoring the black color) any ideas how to do that with Gradient color? thanks!
You can make a gradient layer and apply an image or a shape layer as its mask. Here is a playground.
import PlaygroundSupport
import UIKit
class V: UIView {
private lazy var gradientLayer: CAGradientLayer = {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.red.cgColor,
UIColor.purple.cgColor,
UIColor.blue.cgColor,
UIColor.white.cgColor]
gradientLayer.locations = [0, 0.3, 0.9, 1]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 0, y: 1)
gradientLayer.mask = self.strokeLayer
self.layer.addSublayer(gradientLayer)
return gradientLayer
}()
private lazy var strokeLayer: CAShapeLayer = {
let strokeLayer = CAShapeLayer()
strokeLayer.path = UIBezierPath(ovalIn: CGRect(x:0, y: 0, width: 100, height: 100)).cgPath
return strokeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
strokeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
gradientLayer.frame = bounds
layer.addSublayer(gradientLayer)
}
}
let v = V(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v
I'm not 100% sure I understand the question, but it seems like you want to fill any-old shape with a gradient, right? If so, there are a couple of ways to do that, but the easiest is to make a gradient that's the same size as the boundary of the shape and then apply that as its color. I'm typing this on my PC so I'm sure there's syntax errors, but here goes...
let size = CGSize(width, height)
UIGraphicsRenderer(size, false, 0) // I KNOW I have this one wrong
let colors = [tColour.cgColor, bColour.cgColor] as CFArray
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors , locations: nil)
Set the colors array as needed and then send that into the UIImage. You can use locations: to change the orientation.

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 ...
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
}
}
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 ]
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 ]
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
In storyboard, place a UIView where you want
In storyboard, change the class to "GlowBall" instead of UIView
You're done!
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
}
}
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
#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.
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.
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
}

How to Apply Gradient to background view of iOS Swift App

I'm trying to apply a gradient as the background color of a View (main view of a storyboard). The code runs, but nothing changes. I'm using xCode Beta 2 and Swift.
Here's the code:
class Colors {
let colorTop = UIColor(red: 192.0/255.0, green: 38.0/255.0, blue: 42.0/255.0, alpha: 1.0)
let colorBottom = UIColor(red: 35.0/255.0, green: 2.0/255.0, blue: 2.0/255.0, alpha: 1.0)
let gl: CAGradientLayer
init() {
gl = CAGradientLayer()
gl.colors = [ colorTop, colorBottom]
gl.locations = [ 0.0, 1.0]
}
}
then in the view controller:
let colors = Colors()
func refresh() {
view.backgroundColor = UIColor.clearColor()
var backgroundLayer = colors.gl
backgroundLayer.frame = view.frame
view.layer.insertSublayer(backgroundLayer, atIndex: 0)
}
}
}
Xcode 11 • Swift 5.1
You can design your own Gradient View as follow:
#IBDesignable
public class Gradient: UIView {
#IBInspectable var startColor: UIColor = .black { didSet { updateColors() }}
#IBInspectable var endColor: UIColor = .white { didSet { updateColors() }}
#IBInspectable var startLocation: Double = 0.05 { didSet { updateLocations() }}
#IBInspectable var endLocation: Double = 0.95 { didSet { updateLocations() }}
#IBInspectable var horizontalMode: Bool = false { didSet { updatePoints() }}
#IBInspectable var diagonalMode: Bool = false { didSet { updatePoints() }}
override public class var layerClass: AnyClass { CAGradientLayer.self }
var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }
func updatePoints() {
if horizontalMode {
gradientLayer.startPoint = diagonalMode ? .init(x: 1, y: 0) : .init(x: 0, y: 0.5)
gradientLayer.endPoint = diagonalMode ? .init(x: 0, y: 1) : .init(x: 1, y: 0.5)
} else {
gradientLayer.startPoint = diagonalMode ? .init(x: 0, y: 0) : .init(x: 0.5, y: 0)
gradientLayer.endPoint = diagonalMode ? .init(x: 1, y: 1) : .init(x: 0.5, y: 1)
}
}
func updateLocations() {
gradientLayer.locations = [startLocation as NSNumber, endLocation as NSNumber]
}
func updateColors() {
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
}
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updatePoints()
updateLocations()
updateColors()
}
}
The Colors you're providing to gradient must be of type CGColor. So set your array of CGColor to gl.colors.
The correct code is :
class Colors {
var gl:CAGradientLayer!
init() {
let colorTop = UIColor(red: 192.0 / 255.0, green: 38.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 35.0 / 255.0, green: 2.0 / 255.0, blue: 2.0 / 255.0, alpha: 1.0).cgColor
self.gl = CAGradientLayer()
self.gl.colors = [colorTop, colorBottom]
self.gl.locations = [0.0, 1.0]
}
}
Just modifying the above mentioned answer.
func setGradientBackground() {
let colorTop = UIColor(red: 255.0/255.0, green: 149.0/255.0, blue: 0.0/255.0, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 255.0/255.0, green: 94.0/255.0, blue: 58.0/255.0, alpha: 1.0).cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorTop, colorBottom]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at:0)
}
Then call this method within viewWillAppear
override func viewWillAppear(_ animated: Bool) {
setGradientBackground()
super.viewWillAppear(animated)
}
And if you need to change the direction of the gradient you have to use startPoint and endPoint.
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
gradient.endPoint = CGPoint(x: 1.0, y: 1.0)
gradient.frame = CGRect(x: 0.0, y: 0.0, width: self.view.frame.size.width, height: self.view.frame.size.height)
self.view.layer.insertSublayer(gradient, at: 0)
In Swift3 try this:
func addGradient(){
let gradient:CAGradientLayer = CAGradientLayer()
gradient.frame.size = self.viewThatHoldsGradient.frame.size
gradient.colors = [UIColor.white.cgColor,UIColor.white.withAlphaComponent(0).cgColor] //Or any colors
self.viewThatHoldsGradient.layer.addSublayer(gradient)
}
I have these extensions:
#IBDesignable class GradientView: UIView {
#IBInspectable var firstColor: UIColor = UIColor.red
#IBInspectable var secondColor: UIColor = UIColor.green
#IBInspectable var vertical: Bool = true
lazy var gradientLayer: CAGradientLayer = {
let layer = CAGradientLayer()
layer.colors = [firstColor.cgColor, secondColor.cgColor]
layer.startPoint = CGPoint.zero
return layer
}()
//MARK: -
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyGradient()
}
override init(frame: CGRect) {
super.init(frame: frame)
applyGradient()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
applyGradient()
}
override func layoutSubviews() {
super.layoutSubviews()
updateGradientFrame()
}
//MARK: -
func applyGradient() {
updateGradientDirection()
layer.sublayers = [gradientLayer]
}
func updateGradientFrame() {
gradientLayer.frame = bounds
}
func updateGradientDirection() {
gradientLayer.endPoint = vertical ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0)
}
}
#IBDesignable class ThreeColorsGradientView: UIView {
#IBInspectable var firstColor: UIColor = UIColor.red
#IBInspectable var secondColor: UIColor = UIColor.green
#IBInspectable var thirdColor: UIColor = UIColor.blue
#IBInspectable var vertical: Bool = true {
didSet {
updateGradientDirection()
}
}
lazy var gradientLayer: CAGradientLayer = {
let layer = CAGradientLayer()
layer.colors = [firstColor.cgColor, secondColor.cgColor, thirdColor.cgColor]
layer.startPoint = CGPoint.zero
return layer
}()
//MARK: -
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyGradient()
}
override init(frame: CGRect) {
super.init(frame: frame)
applyGradient()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
applyGradient()
}
override func layoutSubviews() {
super.layoutSubviews()
updateGradientFrame()
}
//MARK: -
func applyGradient() {
updateGradientDirection()
layer.sublayers = [gradientLayer]
}
func updateGradientFrame() {
gradientLayer.frame = bounds
}
func updateGradientDirection() {
gradientLayer.endPoint = vertical ? CGPoint(x: 0, y: 1) : CGPoint(x: 1, y: 0)
}
}
#IBDesignable class RadialGradientView: UIView {
#IBInspectable var outsideColor: UIColor = UIColor.red
#IBInspectable var insideColor: UIColor = UIColor.green
override func awakeFromNib() {
super.awakeFromNib()
applyGradient()
}
func applyGradient() {
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)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
#if TARGET_INTERFACE_BUILDER
applyGradient()
#endif
}
}
Usage:
I made an UIView extension to apply a basic gradient to any view
extension UIView {
func layerGradient() {
let layer : CAGradientLayer = CAGradientLayer()
layer.frame.size = self.frame.size
layer.frame.origin = CGPointMake(0.0,0.0)
layer.cornerRadius = CGFloat(frame.width / 20)
let color0 = UIColor(red:250.0/255, green:250.0/255, blue:250.0/255, alpha:0.5).CGColor
let color1 = UIColor(red:200.0/255, green:200.0/255, blue: 200.0/255, alpha:0.1).CGColor
let color2 = UIColor(red:150.0/255, green:150.0/255, blue: 150.0/255, alpha:0.1).CGColor
let color3 = UIColor(red:100.0/255, green:100.0/255, blue: 100.0/255, alpha:0.1).CGColor
let color4 = UIColor(red:50.0/255, green:50.0/255, blue:50.0/255, alpha:0.1).CGColor
let color5 = UIColor(red:0.0/255, green:0.0/255, blue:0.0/255, alpha:0.1).CGColor
let color6 = UIColor(red:150.0/255, green:150.0/255, blue:150.0/255, alpha:0.1).CGColor
layer.colors = [color0,color1,color2,color3,color4,color5,color6]
self.layer.insertSublayer(layer, atIndex: 0)
}
}
Try This , It's working for me,
var gradientView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 35))
let gradientLayer:CAGradientLayer = CAGradientLayer()
gradientLayer.frame.size = self.gradientView.frame.size
gradientLayer.colors =
[UIColor.white.cgColor,UIColor.red.withAlphaComponent(1).cgColor]
//Use diffrent colors
gradientView.layer.addSublayer(gradientLayer)
You can add starting and end point of gradient color.
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
For more detail description refer Best Answer or you can follow CAGradientLayer From Apple
Hopes This is help for some one.
It's easy
// MARK: - Gradient
extension CAGradientLayer {
enum Point {
case topLeft
case centerLeft
case bottomLeft
case topCenter
case center
case bottomCenter
case topRight
case centerRight
case bottomRight
var point: CGPoint {
switch self {
case .topLeft:
return CGPoint(x: 0, y: 0)
case .centerLeft:
return CGPoint(x: 0, y: 0.5)
case .bottomLeft:
return CGPoint(x: 0, y: 1.0)
case .topCenter:
return CGPoint(x: 0.5, y: 0)
case .center:
return CGPoint(x: 0.5, y: 0.5)
case .bottomCenter:
return CGPoint(x: 0.5, y: 1.0)
case .topRight:
return CGPoint(x: 1.0, y: 0.0)
case .centerRight:
return CGPoint(x: 1.0, y: 0.5)
case .bottomRight:
return CGPoint(x: 1.0, y: 1.0)
}
}
}
convenience init(start: Point, end: Point, colors: [CGColor], type: CAGradientLayerType) {
self.init()
self.startPoint = start.point
self.endPoint = end.point
self.colors = colors
self.locations = (0..<colors.count).map(NSNumber.init)
self.type = type
}
}
Use like this:-
let fistColor = UIColor.white
let lastColor = UIColor.black
let gradient = CAGradientLayer(start: .topLeft, end: .topRight, colors: [fistColor.cgColor, lastColor.cgColor], type: .radial)
gradient.frame = yourView.bounds
yourView.layer.addSublayer(gradient)
Extend UIView with this custom class.
GradientView.swift
import UIKit
class GradientView: UIView {
// Default Colors
var colors:[UIColor] = [UIColor.redColor(), UIColor.blueColor()]
override func drawRect(rect: CGRect) {
// Must be set when the rect is drawn
setGradient(colors[0], color2: colors[1])
}
func setGradient(color1: UIColor, color2: UIColor) {
let context = UIGraphicsGetCurrentContext()
let gradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [color1.CGColor, color2.CGColor], [0, 1])!
// Draw Path
let path = UIBezierPath(rect: CGRectMake(0, 0, frame.width, frame.height))
CGContextSaveGState(context)
path.addClip()
CGContextDrawLinearGradient(context, gradient, CGPointMake(frame.width / 2, 0), CGPointMake(frame.width / 2, frame.height), CGGradientDrawingOptions())
CGContextRestoreGState(context)
}
override func layoutSubviews() {
// Ensure view has a transparent background color (not required)
backgroundColor = UIColor.clearColor()
}
}
Usage
gradientView.colors = [UIColor.blackColor().colorWithAlphaComponent(0.8), UIColor.clearColor()]
Result
Swift 4
Add a view outlet
#IBOutlet weak var gradientView: UIView!
Add gradient to the view
func setGradient() {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
gradient.endPoint = CGPoint(x: 1.0, y: 1.0)
gradient.frame = gradientView.layer.frame
gradientView.layer.insertSublayer(gradient, at: 0)
}
This code will work with Swift 3.0
class GradientView: UIView {
override open class var layerClass: AnyClass {
get{
return CAGradientLayer.classForCoder()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let gradientLayer = self.layer as! CAGradientLayer
let color1 = UIColor.white.withAlphaComponent(0.1).cgColor as CGColor
let color2 = UIColor.white.withAlphaComponent(0.9).cgColor as CGColor
gradientLayer.locations = [0.60, 1.0]
gradientLayer.colors = [color2, color1]
}
}
I mixed the Rohit Sisodia and MGM answers
// MARK: - Gradient
public enum CAGradientPoint {
case topLeft
case centerLeft
case bottomLeft
case topCenter
case center
case bottomCenter
case topRight
case centerRight
case bottomRight
var point: CGPoint {
switch self {
case .topLeft:
return CGPoint(x: 0, y: 0)
case .centerLeft:
return CGPoint(x: 0, y: 0.5)
case .bottomLeft:
return CGPoint(x: 0, y: 1.0)
case .topCenter:
return CGPoint(x: 0.5, y: 0)
case .center:
return CGPoint(x: 0.5, y: 0.5)
case .bottomCenter:
return CGPoint(x: 0.5, y: 1.0)
case .topRight:
return CGPoint(x: 1.0, y: 0.0)
case .centerRight:
return CGPoint(x: 1.0, y: 0.5)
case .bottomRight:
return CGPoint(x: 1.0, y: 1.0)
}
}
}
extension CAGradientLayer {
convenience init(start: CAGradientPoint, end: CAGradientPoint, colors: [CGColor], type: CAGradientLayerType) {
self.init()
self.frame.origin = CGPoint.zero
self.startPoint = start.point
self.endPoint = end.point
self.colors = colors
self.locations = (0..<colors.count).map(NSNumber.init)
self.type = type
}
}
extension UIView {
func layerGradient(startPoint:CAGradientPoint, endPoint:CAGradientPoint ,colorArray:[CGColor], type:CAGradientLayerType ) {
let gradient = CAGradientLayer(start: .topLeft, end: .topRight, colors: colorArray, type: type)
gradient.frame.size = self.frame.size
self.layer.insertSublayer(gradient, at: 0)
}
}
To Use write:-
btnUrdu.layer.cornerRadius = 25
btnUrdu.layer.masksToBounds = true
btnUrdu.layerGradient(startPoint: .centerRight, endPoint: .centerLeft, colorArray: [UIColor.appBlue.cgColor, UIColor.appLightBlue.cgColor], type: .axial)
Output:
if you want to use HEX instead of RGBA, just drag a new empty .swift and add below mentioned code:
import UIKit
extension UIColor {
convenience init(rgba: String) {
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var alpha: CGFloat = 1.0
if rgba.hasPrefix("#") {
let index = advance(rgba.startIndex, 1)
let hex = rgba.substringFromIndex(index)
let scanner = NSScanner(string: hex)
var hexValue: CUnsignedLongLong = 0
if scanner.scanHexLongLong(&hexValue) {
switch (count(hex)) {
case 3:
red = CGFloat((hexValue & 0xF00) >> 8) / 15.0
green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0
blue = CGFloat(hexValue & 0x00F) / 15.0
case 4:
red = CGFloat((hexValue & 0xF000) >> 12) / 15.0
green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0
blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0
alpha = CGFloat(hexValue & 0x000F) / 15.0
case 6:
red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0
green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0
blue = CGFloat(hexValue & 0x0000FF) / 255.0
case 8:
red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0
green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0
blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0
alpha = CGFloat(hexValue & 0x000000FF) / 255.0
default:
print("Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8")
}
} else {
println("Scan hex error")
}
} else {
print("Invalid RGB string, missing '#' as prefix")
}
self.init(red:red, green:green, blue:blue, alpha:alpha)
}
}
similarly, drag another empty .swift file and add below mentioned code:
class Colors {
let colorTop = UIColor(rgba: "##8968CD").CGColor
let colorBottom = UIColor(rgba: "#5D478B").CGColor
let gl: CAGradientLayer
init() {
gl = CAGradientLayer()
gl.colors = [ colorTop, colorBottom]
gl.locations = [ 0.0, 1.0]
}
}
after that in view controller, under class instantiate your 'Color' class like this:
let colors = Colors()
add a new function:
func refresh() {
view.backgroundColor = UIColor.clearColor()
var backgroundLayer = colors.gl
backgroundLayer.frame = view.frame
view.layer.insertSublayer(backgroundLayer, atIndex: 0)
}
state that function in viewDidLoad:
refresh()
you're done :))
using HEX is way too easy if compared to RGBA. :D
Swift 3 - Uses only textures and SKSpriteNode, doesn't require UIView
import Foundation
import SpriteKit
class GradientSpriteNode : SKSpriteNode
{
convenience init(size: CGSize, colors: [UIColor], locations: [CGFloat])
{
let texture = GradientSpriteNode.texture(size: size, colors: colors, locations: locations)
self.init(texture: texture, color:SKColor.clear, size: texture.size())
}
private override init(texture: SKTexture!, color: SKColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private static func texture(size: CGSize, colors: [UIColor], locations: [CGFloat]) -> SKTexture
{
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()!
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors.map{$0.cgColor} as CFArray, locations: locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: size.width / 2, y: 0), end: CGPoint(x: size.width / 2, y: size.height), options: CGGradientDrawingOptions())
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return SKTexture(image: image!)
}
}
Usage:
let gradient = GradientSpriteNode(
size: CGSize(width: 100, height: 100),
colors: [UIColor.red, UIColor.blue],
locations: [0.0, 1.0])
addChild(gradient)
I wanted to add a gradient to a view, and then anchor it using auto-layout.
class GradientView: UIView {
private let gradient: CAGradientLayer = {
let layer = CAGradientLayer()
let topColor: UIColor = UIColor(red:0.98, green:0.96, blue:0.93, alpha:0.5)
let bottomColor: UIColor = UIColor.white
layer.colors = [topColor.cgColor, bottomColor.cgColor]
layer.locations = [0,1]
return layer
}()
init() {
super.init(frame: .zero)
gradient.frame = frame
layer.insertSublayer(gradient, at: 0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = bounds
}
}
Xcode 11 | Swift 5
If anybody is looking for a quick and easy way to add a gradient to a view:
extension UIView {
func addGradient(colors: [UIColor] = [.blue, .white], locations: [NSNumber] = [0, 2], startPoint: CGPoint = CGPoint(x: 0.0, y: 1.0), endPoint: CGPoint = CGPoint(x: 1.0, y: 1.0), type: CAGradientLayerType = .axial){
let gradient = CAGradientLayer()
gradient.frame.size = self.frame.size
gradient.frame.origin = CGPoint(x: 0.0, y: 0.0)
// Iterates through the colors array and casts the individual elements to cgColor
// Alternatively, one could use a CGColor Array in the first place or do this cast in a for-loop
gradient.colors = colors.map{ $0.cgColor }
gradient.locations = locations
gradient.startPoint = startPoint
gradient.endPoint = endPoint
// Insert the new layer at the bottom-most position
// This way we won't cover any other elements
self.layer.insertSublayer(gradient, at: 0)
}
}
Examples on how to use the extension:
// Testing
view.addGradient()
// Two Colors
view.addGradient(colors: [.init(rgb: 0x75BBDB), .black], locations: [0, 3])
// Full Blown
view.addGradient(colors: [.init(rgb: 0x75BBDB), .black], locations: [0, 3], startPoint: CGPoint(x: 0.0, y: 1.5), endPoint: CGPoint(x: 1.0, y: 2.0), type: .axial)
Optionally, use the following to input hex numbers .init(rgb: 0x75BBDB)
extension UIColor {
convenience init(red: Int, green: Int, blue: Int) {
self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
}
convenience init(rgb: Int) {
self.init(
red: (rgb >> 16) & 0xFF,
green: (rgb >> 8) & 0xFF,
blue: rgb & 0xFF
)
}
}
Use below code :
extension UIView {
func applyGradient(colours: [UIColor]) -> Void {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = CGPoint(x : 0.0, y : 0.5)
gradient.endPoint = CGPoint(x :1.0, y: 0.5)
self.layer.insertSublayer(gradient, at: 0)
}
}
call this function like :
self.mainView.applyGradient(colours: [.green, .blue])
To add gradient into layer, add:
let layer = CAGradientLayer()
layer.frame = CGRect(x: 64, y: 64, width: 120, height: 120)
layer.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
view.layer.addSublayer(layer)
Just Specify the Frame of the View, where you want to show the gradient color.
let firstColor = UIColor(red: 69/255, green: 90/255, blue: 195/255, alpha: 1.0).CGColor
let secondColor = UIColor(red: 230/255, green: 44/255, blue: 75/255, alpha: 1.0).CGColor
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [ firstColor, secondColor]
gradientLayer.locations = [ 0.0, 1.0]
gradientLayer.frame = CGRectMake(0, 0, 375, 64)// You can mention frame here
self.view.layer.addSublayer(gradientLayer)
Here's a variation for setting this up in a reusable Util class file
In your Xcode project:
Create a new Swift class call it UI_Util.swift, and populate it as follows:
import Foundation
import UIKit
class UI_Util {
static func setGradientGreenBlue(uiView: UIView) {
let colorTop = UIColor(red: 15.0/255.0, green: 118.0/255.0, blue: 128.0/255.0, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 84.0/255.0, green: 187.0/255.0, blue: 187.0/255.0, alpha: 1.0).cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [ colorTop, colorBottom]
gradientLayer.locations = [ 0.0, 1.0]
gradientLayer.frame = uiView.bounds
uiView.layer.insertSublayer(gradientLayer, at: 0)
}
}
Now you can call the function from any ViewController like so:
class AbcViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UI_Util.setGradientGreen(uiView: self.view)
}
Thanks to katwal-Dipak's answer for the function code
For swift to apply CAGradientLayer to any object (Horizontal and vertical)
func makeGradientColor(`for` object : AnyObject , startPoint : CGPoint , endPoint : CGPoint) -> CAGradientLayer {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [(UIColor(red: 59.0/255.0, green: 187.0/255.0, blue: 182.0/255.0, alpha: 1.00).cgColor), (UIColor(red: 57.0/255.0, green: 174.0/255.0, blue: 236.0/255.0, alpha: 1.00).cgColor)]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = CGRect(x: 0.0, y: 0.0, width: object.frame.size.width, height: object.frame.size.height)
return gradient
}
How to use
let start : CGPoint = CGPoint(x: 0.0, y: 1.0)
let end : CGPoint = CGPoint(x: 1.0, y: 1.0)
let gradient: CAGradientLayer = self.makeGradientColor(for: vwTop, startPoint: start, endPoint: end)
vwTop.layer.insertSublayer(gradient, at: 0)
let start1 : CGPoint = CGPoint(x: 1.0, y: 1.0)
let end1 : CGPoint = CGPoint(x: 1.0, y: 0.0)
let gradient1: CAGradientLayer = self.makeGradientColor(for: vwTop, startPoint: start1, endPoint: end1)
vwBottom.layer.insertSublayer(gradient1, at: 0)
You can check output here
There is a library called Chameleon (https://github.com/viccalexander/Chameleon) which one can use for gradient colors. It even has styles of gradient to implement. This is how you can add it in swift 4 podfile
pod 'ChameleonFramework/Swift', :git => 'https://github.com/ViccAlexander/Chameleon.git', :branch => 'wip/swift4'
import ChameleonFramework
let colors:[UIColor] = [
UIColor.flatPurpleColorDark(),
UIColor.flatWhiteColor()
]
view.backgroundColor = GradientColor(.TopToBottom, frame: view.frame, colors: colors)
Here's a swift extension where you can pass any amount of arbitrary colors. It will remove any previous gradients before inserting one and it will return the newly inserted gradient layer for further manipulation if needed:
extension UIView {
/**
Given an Array of CGColor, it will:
- Remove all sublayers of type CAGradientLayer.
- Create and insert a new CAGradientLayer.
- Parameters:
- colors: An Array of CGColor with the colors for the gradient fill
- Returns: The newly created gradient CAGradientLayer
*/
func layerGradient(colors c:[CGColor])->CAGradientLayer {
self.layer.sublayers = self.layer.sublayers?.filter(){!($0 is CAGradientLayer)}
let layer : CAGradientLayer = CAGradientLayer()
layer.frame.size = self.frame.size
layer.frame.origin = CGPointZero
layer.colors = c
self.layer.insertSublayer(layer, atIndex: 0)
return layer
}
}
Cleaner code that lets you pass any UIColor to an instance of the GradientLayer class:
class GradientLayer {
let gradientLayer: CAGradientLayer
let colorTop: CGColor
let colorBottom: CGColor
init(colorTop: UIColor, colorBottom: UIColor) {
self.colorTop = colorTop.CGColor
self.colorBottom = colorBottom.CGColor
gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorTop, colorBottom]
gradientLayer.locations = [0.0, 1.0]
}
}
Easy to use extension on swift 3
extension CALayer {
func addGradienBorder(colors:[UIColor] = [UIColor.red,UIColor.blue], width:CGFloat = 1) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(origin: .zero, size: self.bounds.size)
gradientLayer.startPoint = CGPoint(x:0.0, y:0.5)
gradientLayer.endPoint = CGPoint(x:1.0, y:0.5)
gradientLayer.colors = colors.map({$0.cgColor})
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = width
shapeLayer.path = UIBezierPath(rect: self.bounds).cgPath
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.black.cgColor
gradientLayer.mask = shapeLayer
self.addSublayer(gradientLayer)
}
}
use to your view, example
yourView.addGradienBorder(color: UIColor.black, opacity: 0.1, offset: CGSize(width:2 , height: 5), radius: 3, viewCornerRadius: 3.0)
If you have view Collection (Multiple View) do this
func setGradientBackground() {
let v:UIView
for v in viewgradian
//here viewgradian is your view Collection Outlet name
{
let layer:CALayer
var arr = [AnyObject]()
for layer in v.layer.sublayers!
{
arr.append(layer)
}
let colorTop = UIColor(red: 216.0/255.0, green: 240.0/255.0, blue: 244.0/255.0, alpha: 1.0).cgColor
let colorBottom = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0).cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [ colorBottom, colorTop]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.frame = v.bounds
v.layer.insertSublayer(gradientLayer, at: 0)
}
}
For those wanting an Objective C version of the answer. Tested and works on iOS13
// Done here so that constraints have completed and the frame is correct.
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
UIColor *colorTop = [UIColor colorWithRed:(CGFloat)192.0/255.0 green: 38.0/255.0 blue: 42.0/255.0 alpha:1.0];
UIColor *colorBottom = [UIColor colorWithRed: 35.0/255.0 green: 2.0/255.0 blue: 2.0/255.0 alpha: 1.0];
CAGradientLayer *gl = [CAGradientLayer new];
[gl setColors:#[(id)[colorTop CGColor], (id)[colorBottom CGColor]]];
[gl setLocations:#[#0.0f, #1.0f]];
self.view.backgroundColor = [UIColor clearColor];
CALayer *backgroundLayer = gl;
backgroundLayer.frame = self.view.frame;
[self.view.layer insertSublayer:backgroundLayer atIndex:0];
}
One thing I noticed is you can't add a gradient to a UILabel without clearing the text. One simple workaround is to use a UIButton and disable user interaction.
SwiftUI: You can use the LinearGradient struct as the first element in a ZStack. As the "bottom" of the ZStack, it will serve as the background color. AngularGradient and RadialGradient are also available.
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
// Put other content here; it will appear on top of the background gradient
}
}
}

Resources