8

I'm trying to apply a gradient to a view which is constraint to the top, left and right of the main screen but for some reason the gradient doesn't cover the whole width of the view that is applied to (see the yellow in the picture).

class ViewController: UIViewController {

    @IBOutlet weak var myView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let gradient = CAGradientLayer()
        gradient.colors = [UIColor.blue.cgColor, UIColor.white.cgColor]
        gradient.startPoint = CGPoint(x:00, y:00)
        gradient.endPoint = CGPoint(x:0, y:0.6)
        gradient.frame = myView.bounds
        myView.clipsToBounds = true
        myView.layer.insertSublayer(gradient, at: 0)
    }
}

What am I doing wrong?

enter image description here

fs_tigre
  • 10,650
  • 13
  • 73
  • 146

5 Answers5

30

The problem is likely that you are adding the gradient layer in viewDidLoad(). A view doesn't get set to it's final size until after viewDidLoad().

Your view controller may be set up in your XIB/Storyboard for a different screen-size than you're running it on. (Say you have it set to iPhone SE size, but you're running it on a 6. The 6's screen is a little wider, so the layer will get set to the width of the iPhone SE, when the view is first loaded. Then the view will be resized, but the layer's size will never be adjusted.)

You should implement the UIViewController method viewDidLayoutSubviews(), and in that method, adjust the layer's frame:

override func viewDidLayoutSubviews() {
  gradientLayer.frame = self.view.bounds
}

That way if the view gets resized (due to auto-rotation, for example) the gradient layer will be automatically adjusted accordingly.

EDIT:

As pointed out by Sulthan, changes to a layer's frame are animated by default. You should probably wrap the frame change in a CATransaction.begin/CATransaction.end and disable actions, like below:

override func viewDidLayoutSubviews() {
  CATransaction.begin()
  CATransaction.setDisableActions(true)
  gradientLayer.frame = self.view.bounds
  CATransaction.commit()
}
Community
  • 1
  • 1
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • By the way, the layer frame animates by default so it might be a good idea to wrap that into `CATransaction` and call `setDisableActions(false)`. – Sulthan Jun 24 '17 at 15:01
  • @ Duncan C - I just moved my code to `viewDidLayoutSubviews()` and it worked. – fs_tigre Jun 24 '17 at 15:09
  • What if I am adding gradient in a view(or tableview cell)? Now when do I set frame of layer? – Nikhil Manapure Mar 01 '19 at 13:10
  • in DrawRect. Thanks anyways :) – Nikhil Manapure Mar 01 '19 at 13:29
  • In general you should avoid using `drawRect()` if at all possible. It is usually slower and more processor-intensive than using layers and letting them draw themselves. I would suggest creating a custom subclass of UITableViewCell and giving it a didSet method on it's frame property. In that method, update the frame of the cell's gradient layer. – Duncan C Mar 01 '19 at 13:56
  • thank you so much .... it's saving my time these "edit" code help me a lot thanks again..!!! – Dhara Desai Dec 19 '19 at 07:50
3

simply put you gredient layer in main thred like this

 DispatchQueue.main.async {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = self.viewGredient.bounds
        gradientLayer.colors = [UIColor.black.cgColor, UIColor.white.cgColor]
        gradientLayer.locations = [0.0, 1.0]
        self.viewGredient.layer.addSublayer(gradientLayer)
    }
2

You can turn it to a UIView. So it will resize automatically and can be seen directly in the Storyboard:

@IBDesignable
final class GradientView: UIView {

    @IBInspectable var firstColor: UIColor = .clear { didSet { updateView() } }
    @IBInspectable var secondColor: UIColor = .clear { didSet { updateView() } }

    @IBInspectable var startPoint: CGPoint = CGPoint(x: 0, y: 0) { didSet { updateView() } }
    @IBInspectable var endPoint: CGPoint = CGPoint(x: 1, y: 1) { didSet { updateView() } }

    override class var layerClass: AnyClass { get { CAGradientLayer.self } }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateView()
        layer.frame = bounds
    }

    private func updateView() {
        let layer = self.layer as! CAGradientLayer
        layer.colors = [firstColor, secondColor].map {$0.cgColor}
        layer.startPoint = startPoint
        layer.endPoint = endPoint
    }
}

Preview

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
1

You needn't set the start and end point, given your goal is to have the gradient span the entire view. You're already setting that with `

gradientLayer.frame = self.view.bounds

`

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var myView: UIView!
    var gradientLayer: CAGradientLayer!
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        createGradientLayer()
    }

    func createGradientLayer() {
        gradientLayer = CAGradientLayer()

        gradientLayer.frame = self.view.bounds

        gradientLayer.colors = [UIColor.blueColor().CGColor, UIColor.whiteColor().CGColor]

        self.view.layer.addSublayer(gradientLayer)
    }
}
Martin Muldoon
  • 3,388
  • 4
  • 24
  • 55
  • The view controller `viewWillAppear(animated:)` function will be called each time the view controller is revealed (e.g. if you push another view controller on top of it, then pop that new view controller, or present a full-screen modal then dismiss it. If you’re going to add a layer in viewWillAppear then you need to check to see if it exists already. Also, this won’t handle situations when the view’s frame changes (like device rotation.) It’s better to add the layer in viewDidLoad and update it’s size in `viewDidLayoutSubviews()` as I suggest in my answer. – Duncan C Dec 27 '22 at 15:42
0

You can simplify it by CustomClass with one line of code

class GradientView: UIView {
   override class var layerClass: Swift.AnyClass {
      return CAGradientLayer.self
   }
}


// MARK: Usage

@IBOutlet weak var myView: GradientView!

guard let gradientLayer = myView.layer as? CAGradientLayer else { return }
gradient.colors = [UIColor.blue.cgColor, UIColor.white.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
gradient.endPoint = CGPoint(x:0, y:0.6)
Leonif
  • 466
  • 4
  • 17