6

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,075
  • 12
  • 64
  • 127

4 Answers4

27

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
  • 122,346
  • 22
  • 162
  • 258
  • 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
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,180
  • 4
  • 21
  • 50
1

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
  • 71,072
  • 19
  • 226
  • 225
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
  • 450
  • 4
  • 14