add gradient to UILabel in a collectionView cell - swift - ios

I'm trying to add gradient to an UILabel in a collectionViewCell. To add gradient I'm using this function:
extension UIView
{
func gradient(colors: [Any], startPoint: CGPoint, endPoint: CGPoint, opacity: Float, location: [NSNumber]?) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.opacity = opacity
gradientLayer.locations = location
layer.addSublayer(gradientLayer)
}
}
and in my collectionViewCell :
#IBOutlet weak var discount: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
discount.gradient(colors: [green,blue], startPoint: CGPoint(x: 0.0, y: 0.5), endPoint: CGPoint(x: 1.0, y: 0.5), opacity: 1, location: [0.5,0.5])
}
But it's not showing anything. Adding shadow works fine but adding gradient not showing anything. Why is that happen?

Update your extension to
extension UIView
{
func gradient(colors: [CGColor], startPoint: CGPoint, endPoint: CGPoint, opacity: Float, location: [NSNumber]?) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.opacity = opacity
gradientLayer.locations = location
layer.addSublayer(gradientLayer)
}
}
Call it from collectionView class as
override func layoutSubviews() {
discount.gradient(colors: [UIColor.blue.cgColor, UIColor.green.cgColor], startPoint: CGPoint.init(x: 0.5, y: 0.0), endPoint: CGPoint.init(x: 0.5, y: 1.0), opacity: 1.0, location: [0,1])
}
Tested and working.

Whenever I add a sublayer to a view I like to make sure the frame is always updated in layoutSubviews:
public override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
You'll need to keep a reference to the gradient layer as a property in your view.

If you have used cocoapods, the Chameleon Framework offers a very easy solution to creating gradient colors. To use it, install cocoapods, then install Chameleon Framework. Put
import ChameleonFramework
at the top of the UIViewController.
The link for the usage after the installation is below.
https://github.com/ViccAlexander/Chameleon#gradient-colors-1
It looks like it as is easy as the line of code below if you are using Swift.
UIColor(gradientStyle:UIGradientStyle, withFrame:CGRect, andColors:[UIColor])
Just set the background of your UILabel using that line of code above.

If your goal is making gradient text then you must to the following:
Embed your label in a UIView and connect outlet (say discountContainer)
Add a gradient layer to that view (instead of label).
discountContainer.gradient(colors: [green,blue], startPoint: CGPoint(x: 0.0, y: 0.5), endPoint: CGPoint(x: 1.0, y: 0.5), opacity: 1, location: [0.5,0.5])
Set the label as the views mask
discountContainer.mask = discount
Result:
EDITED:
If you need to make gradient only on the background then you should keep the reference to CAGradientLayer.
extension UIView
{
func gradient(colors: [Any], startPoint: CGPoint, endPoint: CGPoint, opacity: Float, location: [NSNumber]?) -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.opacity = opacity
gradientLayer.locations = location
layer.addSublayer(gradientLayer)
return layer
}
}
and in your collectionViewCell:
#IBOutlet weak var discount: UILabel!
var gradientLayer: CAGradientLayer?
override func awakeFromNib() {
super.awakeFromNib()
gradientLayer = discount.gradient(colors: [green,blue], startPoint: CGPoint(x: 0.0, y: 0.5), endPoint: CGPoint(x: 1.0, y: 0.5), opacity: 1, location: [0.5,0.5])
}
public override func layoutSubviews() {
super.layoutSubviews()
gradientLayer?.frame = bounds
}

Related

UIView with gradient not changing colors when button is tapped

When a user clicks on a button, it randomizes the colors used to create a gradient in a large circle. Beneath it are two small circles that display the solid colors used for the gradient. They all display correctly at the start (main circle is a gradient of the randomized smaller circles' color) but when I click on the button, only the smaller circles change color; the large circle stays at the same gradient colors.
Extensions and View Controller:
extension UIView {
func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.frame = self.bounds
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
extension UIColor {
static var random: UIColor {
return UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1.0)
}
}
class GradientController: UIViewController {
let gradientView = GradientView()
let leftGradientColor: UIColor = .random
let rightGradientColor: UIColor = .random
}
override func viewDidLoad() {
super.viewDidLoad()
view = gradientView
newGradient()
}
func newGradient() {
gradientView.mainCircleView.setupGradientBackground(colorOne: leftGradientColor, colorTwo: rightGradientColor)
gradientView.colorCircleLeftView.backgroundColor = leftGradientColor
gradientView.colorCircleRightView.backgroundColor = rightGradientColor
gradientView.gradientGenerateButton.addTarget(self, action: #selector(randomGradient(sender:)), for: .touchUpInside)
}
#objc func randomGradient(sender: UIButton)
{
let leftGradient = UIColor.random
let rightGradient = UIColor.random
gradientView.colorCircleLeftView.backgroundColor = leftGradient
gradientView.colorCircleRightView.backgroundColor = rightGradient
//Here is where it's not changing colors. Doesn't seem like the VC recognizes it in this function
gradientView.mainCircleView.setupGradientBackground(colorOne: leftGradient, colorTwo: rightGradient)
}
View:
class GradientView: UIView {
//circle's UIView code in Extensions
let mainCircleView = UIView().circleView(width: 380, height: 380)
let colorCircleLeftView = UIView().circleView(width: 40, height: 40)
let colorCircleRightView = UIView().circleView(width: 40, height: 40)
...
func setupLayout() {
...
}
}
What I've tried is changing the mainCircleView color to solid UIColors, like gradientView.mainCircleView.setupGradientBackground(colorOne: .red, colorTwo: .orange) to see if the main Circle changes to those colors in both func newGradient() and #objc func randomGradient(sender: UIButton). It only changes in func newGradient() what I've set manually, so that means the VC isn't recognizing the main Circle in the #objc func but I'm lost on how to fix it...
Any help is appreciated!
What it looks like when I click the "Generate" button (large circle should be showing brown and purple):
Update your function with this, You have to remove old layer and then insert new Sublayer.
Solution 1 :
func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
if let gradientLayer = (self.layer.sublayers?.compactMap { $0 as? CAGradientLayer })?.first {
gradientLayer.removeFromSuperlayer()
}
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.frame = self.bounds
self.layer.insertSublayer(gradientLayer, at: 0)
}
Solution 2 :
func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientLayer = layer.sublayers?.first as? CAGradientLayer ?? CAGradientLayer()
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.frame = self.bounds
guard gradientLayer.superlayer != self else {
return
}
layer.insertSublayer(gradientLayer, at: 0)
}
Solution 3 :
you can set name to your CAGradientLayer, This will help you for removing that particular layer.
func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
for layer in layer.sublayers ?? [] {
if layer.name == "GradientLayer" {
layer.removeFromSuperlayer()
}
}
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
gradientLayer.name = "GradientLayer"
gradientLayer.frame = self.bounds
self.layer.insertSublayer(gradientLayer, at: 0)
}
The gradient does not change because you add a new gradient below the existing one
extension UIView {
func setupGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientLayer: CAGradientLayer = CAGradientLayer()
...
// ⬇ There is a mistake.
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
You should change the current gradient with this new one. Or insert a new one above the old one.
Changing the gradient layer colors after setting it
extension CALayer {
func updateGradientColors(_ colors:CGColor...) {
if let layer = sublayers?.first as? CAGradientLayer {
let frame = CGRect(origin: self.bounds.origin, size: CGSize(width: self.bounds.size.width, height: self.bounds.size.height))
layer.frame = frame
layer.colors = colors
}
}
}

How to apply a gradient on a UI Tab Bar In Swift?

I built the tab bar through the storyboard, and to customise the colour I change it in the app delegate, with UITabBar.appearance().barTintColor = Color,
I have a gradient method which is this:
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientlayer = CAGradientLayer()
gradientlayer.frame = bounds
gradientlayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientlayer.locations = [0, 1]
gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientlayer, at: 0)
}
How do I apply this to the background of my tab bar?
Just create a subclass of UITabBarController
class GradientTabBarController: UITabBarController {
let gradientlayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
setGradientBackground(colorOne: .yellow, colorTwo: .red)
}
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
gradientlayer.frame = tabBar.bounds
gradientlayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientlayer.locations = [0, 1]
gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.0)
self.tabBar.layer.insertSublayer(gradientlayer, at: 0)
}
}
Assign GradientTabBarController class in the storyboard instead of UITabBarController
Main pros of this methodology are below.
No need to define delegate methods of UITabBar
No need to write code in each UIViewController
Step 1
Assuming that you have built a tab bar that way, make sure it's the delegate to your ViewController.
Step 2
In your ViewController.swift use the following code:
import UIKit
class ViewController: UIViewController, UITabBarDelegate {
#IBOutlet weak var tabBar: UITabBar!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setGradientBackground(colorOne: .blue, colorTwo: .red)
}
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientlayer = CAGradientLayer()
gradientlayer.frame = tabBar.bounds
gradientlayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientlayer.locations = [0, 1]
gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.0)
self.tabBar.layer.insertSublayer(gradientlayer, at: 0)
}
}
Result

Creating a clear button with gradient border and gradient text

I'm trying to add a gradient to my UIButton Title and to the border of the button. I've gone through most of the solution on here which I cannot get working for the life of me, might be outdated, I'm not sure. So currently I extend the UIView in order to set the gradient of whatever. So how would I add another function for this feature?
func setGradientBackground(colorOne: UIColor, colorTwo: UIColor) {
let gradientlayer = CAGradientLayer()
gradientlayer.frame = bounds
gradientlayer.colors = [colorOne.cgColor, colorTwo.cgColor]
gradientlayer.locations = [0, 1]
gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.0)
gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientlayer, at: 0)
}
I have created a demo for you, you can do this with the help of CAGradientLayer see the following output and code for this.
Storyboard:
For gradient button text color and border put your UIButton inside UIView, then assign CAGradientLayer to UIview.
Note:- Don't forget to set the button as the views mask, See the following code.
import UIKit
class ViewController: UIViewController {
#IBOutlet var viewForButton: UIView!
#IBOutlet var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Create a gradient layer
let gradient = CAGradientLayer()
// gradient colors in order which they will visually appear
gradient.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
// Gradient from left to right
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
// set the gradient layer to the same size as the view
gradient.frame = viewForButton.bounds
// add the gradient layer to the views layer for rendering
viewForButton.layer.insertSublayer(gradient, at: 0)
// Tha magic! Set the button as the views mask
viewForButton.mask = myButton
//Set corner Radius and border Width of button
myButton.layer.cornerRadius = myButton.frame.size.height / 2
myButton.layer.borderWidth = 5.0
}
}
Extension: You can also prefer this extension for the same.
extension UIView{
func gradientButton(_ buttonText:String, startColor:UIColor, endColor:UIColor) {
let button:UIButton = UIButton(frame: self.bounds)
button.setTitle(buttonText, for: .normal)
let gradient = CAGradientLayer()
gradient.colors = [startColor.cgColor, endColor.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = self.bounds
self.layer.insertSublayer(gradient, at: 0)
self.mask = button
button.layer.cornerRadius = button.frame.size.height / 2
button.layer.borderWidth = 5.0
}
}
How to use:
testView.gradientButton("Hello", startColor: .red, endColor: .blue)
You just need to add below UIView extension and call the function to get desire gradient button,
func covertToGradientButtonWith(title: String, radius: CGFloat, borderWidth: CGFloat, gradientStartColor: UIColor, gradientEndColor: UIColor) {
let button:UIButton = UIButton(frame: self.bounds)
button.setTitle(title, for: .normal)
let gradient = CAGradientLayer()
gradient.colors = [gradientStartColor.cgColor, gradientEndColor.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.frame = self.bounds
self.layer.insertSublayer(gradient, at: 0)
self.mask = button
button.layer.cornerRadius = radius
button.layer.borderWidth = borderWidth
}
Hope, this solution may help you.

horizontal gradient doesn't work on dynamic width content

I am trying to implement gradient for button or view depending on requirement. But whenever I try to do so for button/view who's width is dynamic (for horizontal gradient) it only loads half the same.
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
GradientOrientation is obtained by following code.
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint {
return points.startPoint
}
var endPoint: CGPoint {
return points.endPoint
}
var points: GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint.init(x: 0.0, y: 1.0), CGPoint.init(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint.init(x: 0.0, y: 0.0), CGPoint.init(x: 1, y: 1))
case .horizontal:
return (CGPoint.init(x: 0.0, y: 0.5), CGPoint.init(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint.init(x: 0.0, y: 0.0), CGPoint.init(x: 0.0, y: 1.0))
}
}
}
But if the width is 150 it loads up perfectly for both view/button. Any help is appreciated.
Most likely the frame of your button is not the same when you apply the gradient as it is later on. You can try printing bounds in applyGradient to check.
You should add a bit more logic anyway; since this looks like a subclass of a view it would be great to store the layer. Currently what do you expect will happen if applyGradient is called twice? You will most likely have 2 gradients.
So you would add:
private var currentGradientLayer: CAGradientLayer?
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
currentGradientLayer?.removeFromSuperlayer()
currentGradientLayer = nil
let gradient: CAGradientLayer = {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
}()
self.layer.insertSublayer(gradient, at: 0)
currentGradientLayer = gradient
}
Now since that is done you should try to override 2 more methods to fix the frames:
override func layoutSubviews() {
super.layoutSubviews()
refreshGradientLayer()
}
override var frame: CGRect {
didSet {
refreshGradientLayer()
}
}
private func refreshGradientLayer() {
currentGradientLayer?.frame = bounds
}
This should refresh the gradient in any case; layoutSubviews will be called when your view is layout with constraints and frame when it is done programmatically.
All sublayers in UIView.layer do not change their size automatically when changing the super layer. Override the method of UIView.layoutSubviews() and update bounds CAGradientLayer from UIView.layer.
<gradientlayer>.layoutIfNeeded()
Call this method after you done calculating the width of the dynamic component.

Set Background Gradient on Button in Swift

I have no idea how to set the background gradient on a button (without making the background gradient an image). This is so different from Android.
Here's a class I have to define a returnable gradient scheme:
import UIKit
extension CAGradientLayer {
func backgroundGradientColor() -> CAGradientLayer {
let topColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(51/255.0), alpha: 1)
let bottomColor = UIColor(red: (0/255.0), green: (153/255.0), blue:(255/255.0), alpha: 1)
let gradientColors: [CGColor] = [topColor.CGColor, bottomColor.CGColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColors
gradientLayer.locations = gradientLocations
return gradientLayer
}
}
I can use this to set the background of my entire view with the following:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let background = CAGradientLayer().backgroundGradientColor()
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, atIndex: 0)
}
//...
}
But how can I access the view of the button and insert the sublayer or something like that?
Your code works fine. You just have to remember to set the gradient's frame every time. It is better to just make the gradient category also set the frame of the view for you.
That way you don't forget and it applies fine.
import UIKit
extension UIView {
func applyGradient(colours: [UIColor]) -> CAGradientLayer {
return self.applyGradient(colours: colours, locations: nil)
}
func applyGradient(colours: [UIColor], locations: [NSNumber]?) -> CAGradientLayer {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
return gradient
}
}
class ViewController: UIViewController {
#IBOutlet weak var btn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.btn.applyGradient(colours: [.yellow, .blue])
self.view.applyGradient(colours: [.yellow, .blue, .red], locations: [0.0, 0.5, 1.0])
}
}
Buttons are views. You apply gradients to it the same way you would apply it to any other view.
Picture Proof:
Video Proof:
https://i.imgur.com/ssDTqPu.mp4
It's this simple:
import UIKit
class ActualGradientButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
private lazy var gradientLayer: CAGradientLayer = {
let l = CAGradientLayer()
l.frame = self.bounds
l.colors = [UIColor.systemYellow.cgColor, UIColor.systemPink.cgColor]
l.startPoint = CGPoint(x: 0, y: 0.5)
l.endPoint = CGPoint(x: 1, y: 0.5)
l.cornerRadius = 16
layer.insertSublayer(l, at: 0)
return l
}()
}
Here below you can find the solution for Swift3 (and Swift4 too) and a little bit extended (orientation helper):
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint : CGPoint {
return points.startPoint
}
var endPoint : CGPoint {
return points.endPoint
}
var points : GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0,y: 1.0), CGPoint(x: 1.0,y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 1,y: 1))
case .horizontal:
return (CGPoint(x: 0.0,y: 0.5), CGPoint(x: 1.0,y: 0.5))
case .vertical:
return (CGPoint(x: 0.0,y: 0.0), CGPoint(x: 0.0,y: 1.0))
}
}
}
extension UIView {
func applyGradient(with colours: [UIColor], locations: [NSNumber]? = nil) {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
}
func applyGradient(with colours: [UIColor], gradient orientation: GradientOrientation) {
let gradient = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
}
#Zeb answer is great but just to clean it up and make it a little more swifty.
Computed read-only properties should avoid using get and returning Void is redundant:
typealias GradientPoints = (startPoint: CGPoint, endPoint: CGPoint)
enum GradientOrientation {
case topRightBottomLeft
case topLeftBottomRight
case horizontal
case vertical
var startPoint: CGPoint {
return points.startPoint
}
var endPoint: CGPoint {
return points.endPoint
}
var points: GradientPoints {
switch self {
case .topRightBottomLeft:
return (CGPoint(x: 0.0, y: 1.0), CGPoint(x: 1.0, y: 0.0))
case .topLeftBottomRight:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 1, y: 1))
case .horizontal:
return (CGPoint(x: 0.0, y: 0.5), CGPoint(x: 1.0, y: 0.5))
case .vertical:
return (CGPoint(x: 0.0, y: 0.0), CGPoint(x: 0.0, y: 1.0))
}
}
}
extension UIView {
func applyGradient(withColours colours: [UIColor], locations: [NSNumber]? = nil) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.locations = locations
self.layer.insertSublayer(gradient, at: 0)
}
func applyGradient(withColours colours: [UIColor], gradientOrientation orientation: GradientOrientation) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = orientation.startPoint
gradient.endPoint = orientation.endPoint
self.layer.insertSublayer(gradient, at: 0)
}
}
If you want a gradient background on a button, rather than adding the gradient as a sublayer and changing its frame in layoutSubviews, I would instead just specify the layerClass of the button to be a CAGradientLayer, so the main layer is a gradient:
#IBDesignable
public class GradientButton: UIButton {
public override class var layerClass: AnyClass { CAGradientLayer.self }
private var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }
#IBInspectable public var startColor: UIColor = .white { didSet { updateColors() } }
#IBInspectable public var endColor: UIColor = .red { didSet { updateColors() } }
// expose startPoint and endPoint to IB
#IBInspectable public var startPoint: CGPoint {
get { gradientLayer.startPoint }
set { gradientLayer.startPoint = newValue }
}
#IBInspectable public var endPoint: CGPoint {
get { gradientLayer.endPoint }
set { gradientLayer.endPoint = newValue }
}
// while we're at it, let's expose a few more layer properties so we can easily adjust them in IB
#IBInspectable public var cornerRadius: CGFloat {
get { layer.cornerRadius }
set { layer.cornerRadius = newValue }
}
#IBInspectable public var borderWidth: CGFloat {
get { layer.borderWidth }
set { layer.borderWidth = newValue }
}
#IBInspectable public var borderColor: UIColor? {
get { layer.borderColor.flatMap { UIColor(cgColor: $0) } }
set { layer.borderColor = newValue?.cgColor }
}
// init methods
public override init(frame: CGRect = .zero) {
super.init(frame: frame)
updateColors()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
updateColors()
}
}
private extension GradientButton {
func updateColors() {
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
}
}
By setting the layerClass, it will just make the main layer be a gradient, which automatically is adjusted to the bounds of the button for you. This has an advantage that if you animate the changing of the button size (e.g. on rotation events or whatever), the gradient will be correctly animated, too.
And, it is not necessary, but it may be convenient to make this class an #IBDesignable, so one can set its properties in IB, and it will be correctly rendered in the storyboard/NIB with no additional code in the view controller. For example, I can customize the corners, border, and gradient colors and direction in IB:
Try this is working for me ,
let button = UIButton(frame: CGRect(x: 60, y: 150, width: 200, height: 60))
button.setTitle("Email", for: .normal)
button.backgroundColor = .red
button.setTitleColor(UIColor.black, for: .normal)
button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
// Apply Gradient Color
let gradientLayer:CAGradientLayer = CAGradientLayer()
gradientLayer.frame.size = button.frame.size
gradientLayer.colors =
[UIColor.white.cgColor,UIColor.green.withAlphaComponent(1).cgColor]
//Use diffrent colors
button.layer.addSublayer(gradientLayer)
self.view.addSubview(button)
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 details description refer CAGradientLayer doc
I have tried all of them this is my button init inside of viewdidload
let button = UIButton()
button.setTitle("Alper", for: .normal)
button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1
view.addSubview(button)
button.anchor(top: nil, left: nil, bottom: logo.topAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 50, width: 100)
let gradientx = CAGradientLayer()
gradientx.colors = [UIColor.blue,UIColor.red]
gradientx.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientx.endPoint = CGPoint(x: 1.0, y: 1.0)
gradientx.frame = button.bounds
button.layer.insertSublayer(gradientx, at: 0)
anchor is an extension, so this is irrelevant gradient.
There are already many answers there I want add what I did to achieve this. I use this custom Button GradientButton
import Foundation
import UIKit
class GradientButton: UIButton {
let gradientColors : [UIColor]
let startPoint : CGPoint
let endPoint : CGPoint
required init(gradientColors: [UIColor] = [UIColor.red, UIColor.blue],
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
self.gradientColors = gradientColors
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let halfOfButtonHeight = layer.frame.height / 2
contentEdgeInsets = UIEdgeInsets(top: 10, left: halfOfButtonHeight, bottom: 10, right: halfOfButtonHeight)
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
backgroundColor = UIColor.clear
// setup gradient
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = gradientColors.map { $0.cgColor }
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.cornerRadius = 4
// replace gradient as needed
if let oldGradient = layer.sublayers?[0] as? CAGradientLayer {
layer.replaceSublayer(oldGradient, with: gradient)
} else {
layer.insertSublayer(gradient, below: nil)
}
// setup shadow
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: halfOfButtonHeight).cgPath
layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
layer.shadowOpacity = 0.85
layer.shadowRadius = 4.0
}
override var isHighlighted: Bool {
didSet {
let newOpacity : Float = isHighlighted ? 0.6 : 0.85
let newRadius : CGFloat = isHighlighted ? 6.0 : 4.0
let shadowOpacityAnimation = CABasicAnimation()
shadowOpacityAnimation.keyPath = "shadowOpacity"
shadowOpacityAnimation.fromValue = layer.shadowOpacity
shadowOpacityAnimation.toValue = newOpacity
shadowOpacityAnimation.duration = 0.1
let shadowRadiusAnimation = CABasicAnimation()
shadowRadiusAnimation.keyPath = "shadowRadius"
shadowRadiusAnimation.fromValue = layer.shadowRadius
shadowRadiusAnimation.toValue = newRadius
shadowRadiusAnimation.duration = 0.1
layer.add(shadowOpacityAnimation, forKey: "shadowOpacity")
layer.add(shadowRadiusAnimation, forKey: "shadowRadius")
layer.shadowOpacity = newOpacity
layer.shadowRadius = newRadius
let xScale : CGFloat = isHighlighted ? 1.025 : 1.0
let yScale : CGFloat = isHighlighted ? 1.05 : 1.0
UIView.animate(withDuration: 0.1) {
let transformation = CGAffineTransform(scaleX: xScale, y: yScale)
self.transform = transformation
}
}
}
}
You can make GradientButton instance like this.
let button = GradientButton.init(gradientColors:[UIColor.black, UIColor.white], startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 0, y: 1))
For Swift
extension UIViewController {
func makeGradientColor(`for` object : AnyObject , startPoint : CGPoint , endPoint : CGPoint) -> CAGradientLayer {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.colors = [(UIColor.red.cgColor), (UIColor.yellow.cgColor)]
gradient.locations = [0.0 , 1.0]
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.frame = CGRect(x: 0.0, y: 0.0, width: object.bounds.size.width, height: object.bounds.size.height)
return gradient
}
}
How to use ?
if let layers = btn.layer.sublayers{
for layer in layers {
if layer.isKind(of: CAGradientLayer.self) {
layer.removeFromSuperlayer()
}
}
}
let start : CGPoint = CGPoint(x: 0.0, y: 0.0)
let end : CGPoint = CGPoint(x: 1.0, y: 1.0)
let gradient: CAGradientLayer = self.makeGradientColor(for: cell.bgView, startPoint: start, endPoint: end)
btn.layer.insertSublayer(gradient, at: 0)
I've modified this great answer to improve the reusability of the button by adding init parameters for colors, radius, and gradient direction.
I also added updateGradientColors method as it might be useful if you want to change the gradient color at some point.
class GradientButton: UIButton {
private let colors: [UIColor]
private let cornerRadius: CGFloat
private let startPoint: CGPoint
private let endPoint: CGPoint
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
init(colors: [UIColor],
cornerRadius: CGFloat = 10,
startPoint: CGPoint = CGPoint(x: 0, y: 0.5),
endPoint: CGPoint = CGPoint(x: 1, y: 0.5)) {
self.colors = colors
self.cornerRadius = cornerRadius
self.startPoint = startPoint
self.endPoint = endPoint
super.init(frame: .zero)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
private lazy var gradientLayer: CAGradientLayer = {
let gl = CAGradientLayer()
gl.frame = self.bounds
gl.colors = colors.map { $0.cgColor }
gl.startPoint = startPoint
gl.endPoint = endPoint
gl.cornerRadius = cornerRadius
layer.insertSublayer(gl, at: 0)
return gl
}()
func updateGradientColors(_ colors: [UIColor]) {
gradientLayer.colors = colors.map { $0.cgColor }
}
}
Gradient Button with corner radius, start and End Points Code is here...
extension UIView {
func applyGradient(colours: [UIColor], cornerRadius: CGFloat?, startPoint: CGPoint, endPoint: CGPoint) {
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
if let cornerRadius = cornerRadius {
gradient.cornerRadius = cornerRadius
}
gradient.startPoint = startPoint
gradient.endPoint = endPoint
gradient.colors = colours.map { $0.cgColor }
self.layer.insertSublayer(gradient, at: 0)
}
}
Usage :
self.yourButton.applyGradient(colours: [.red, .green], cornerRadius: 20, startPoint: CGPoint(x: 0, y: 0.5), endPoint: CGPoint(x: 1, y: 0.5))
class ButtonGradient : UIButton {
override func layoutSubviews() {
let layer : CAGradientLayer = CAGradientLayer()
layer.frame.size = self.frame.size
layer.frame.origin = CGPoint(x: 0, y: 0)
// layer.cornerRadius = CGFloat(frame.width / 20)
let color0 = UIColor(red:255/255, green:122/255, blue:0/255, alpha:1.0).cgColor
let color1 = UIColor(red:255/255, green:176/255, blue: 0/255, alpha:1.0).cgColor
let color2 = UIColor(red:250/255, green:98/255, blue: 44/255, alpha:1.0).cgColor
layer.locations = [0.5, 1.0]
layer.startPoint = CGPoint(x: 0.0, y: 0.5)
layer.endPoint = CGPoint(x: 0.5, y: 0.5)
layer.colors = [color2,color0,color1]
self.layer.insertSublayer(layer, at: 0)
}
}
After that directly assign "ButtonGredient" class to particular button in Storyboard.
Here, I have taken one UIView and add button in it.
#IBOutlet weak var btnCenter: UIButton!
#IBOutlet weak var viewCenter: UIView!
// Create a gradient layer
let gradient = CAGradientLayer()
// gradient colors in order which they will visually appear
gradient.colors = [UIColor.yello.cgColor, UIColor.blue.cgColor]
// Gradient from left to right
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
// set the gradient layer to the same size as the view
gradient.frame = viewCenter.bounds
// add the gradient layer to the views layer for rendering
viewCenter.layer.insertSublayer(gradient, at: 0)
// Tha magic! Set the button as the views mask
viewCenter.mask = btnCenter
//Set corner Radius and border Width of button
btnCenter.layer.cornerRadius = btnCenter.frame.size.height / 2
btnCenter.layer.borderWidth = 5.0
There are ways to work with initial layer without making sublayers.
import UIKit
#IBDesignable class GradientButton: UIButton {
#IBInspectable var startColor: UIColor = UIColor.white
#IBInspectable var endColor: UIColor = UIColor.white
#IBInspectable var cornerRadius = CGFloat(5.0)
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
super.layoutSubviews()
//This is an advanced gradient we do not use for now
// (layer as! CAGradientLayer).startPoint = CGPoint(x: 0, y: 0)
// (layer as! CAGradientLayer).endPoint = CGPoint(x: 1, y: 1)
// (layer as! CAGradientLayer).locations = [0,1]
// Simple gradient
(layer as! CAGradientLayer).colors = [startColor.cgColor, endColor.cgColor]
layer.cornerRadius = cornerRadius
}
}
class GradientButton: UIButton {
var gradientLayer: CAGradientLayer? {
didSet {
layer.sublayers?.filter { $0 is CAGradientLayer }.forEach { $0.removeFromSuperlayer() }
if let gradientLayer = gradientLayer {
layer.insertSublayer(gradientLayer, at: 0)
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer?.frame = self.bounds
}
}

Resources