Search code examples
iosswiftcalayercagradientlayer

How to move a CAGradientLayer with its parent UIView container if constraints are not allowed?


Currently I have a UIView container situated above a UITableView. Using the inheritance from UIScrollView, I created an IBOutlet of the container view's leading constraint, and adjust the constraint similar to as follows:

func scrollViewDidScroll(_ scrollView: UIScrollView)
{
    if scrollView.contentOffset.y < 0
    {
        ...
        containerViewLeftConstraint.constant -= abs(scrollView.contentOffset.y) * 2
    }
    else
    {
       ...
       containerViewLeftConstraint.constant += abs(scrollView.contentOffset.y) * 2
    }
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
{
    resetContainerViewSize()
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
{
    resetContainerViewSize()
}

func resetContainerViewSize()
{
    containerViewLeftConstraint.constant = 0.0

    UIView.animate(withDuration: 0.4,
                   delay: 0.0,
                   usingSpringWithDamping: 0.7,
                   initialSpringVelocity: 0.5,
                   options: .curveEaseInOut,
                   animations: {
                    self.view.layoutIfNeeded()
                }, completion: nil)
}

For demonstration, I've set the container view's backgroundColor to red to visually see what's happening:

I've declared a var gradientLayer = CAGradientLayer() and added it as as a sublayer to the container view like so:

func createGradientOverlay()
{
    gradientLayer.frame = containerView.frame

    let colors = [UIColor.black.withAlphaComponent(0.5).cgColor,
                  UIColor.white.cgColor]

    gradientLayer.colors = colors
    gradientLayer.startPoint = CGPoint(x:0.0,
                               y:0.5)
    gradientLayer.endPoint = CGPoint(x:1.0, y:0.5);
    containerView.layer.addSublayer(gradientLayer)
}

However, the result I'm getting is as shown:

The gradient layer does not stick to the bounds of the container view and appears to be floating.

I've looked at several similar questions:

  1. CALayers didn't get resized on its UIView's bounds change. Why?
  2. Auto Layout constraint on CALayer IOS
  3. How to get CAShapeLayer to work with constraints with swift?
  4. How to add a CAGradientLayer to a UIView when using programmatic constraints

All suggests using viewDidLayoutSubviews like so:

override func viewDidLayoutSubviews()
{
    super.viewDidLayoutSubviews()

    gradientLayer.frame = nameContainerView.frame
}

However the gradient layer still appears to be floating and not sticking to the constraints of the container view.

I've also tried the following to no avail:

  1. gradientLayer.frame = nameContainerView.bounds
  2. gradientLayer.bounds = nameContainerView.bounds
  3. gradientLayer.frame = nameContainerView.layer.bounds
  4. gradientLayer.frame = nameContainerView.layer.frame

How can I resolve this issue?


Solution

  • Pangu - If someone wants additional information in an effort to try to help you then you should probably provide the information so that they can help you. Now to your question. You can accomplish this two ways cleanly and if I could see your function probably 3.

    The first would be to create a view that uses a CAGradientLayer as the backing layer and then you could apply autolayout to the Gradient View. Here is a class you could use that took me a few minutes.

    import UIKit
    
    
    class GradientBackedLayer: UIView {
    
        var colors : [UIColor] = [UIColor.black.withAlphaComponent(0.5),UIColor.white]{
            didSet{
                setUpGradient()
            }
        }
    
        var startPoint : CGPoint = CGPoint(x:0.0,y:0.5){
            didSet{
                setUpGradient()
            }
        }
    
        var endPoint : CGPoint = CGPoint(x:1.0, y:0.5){
            didSet{
                setUpGradient()
            }
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setUpGradient()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setUpGradient()
        }
    
        func setUpGradient(){
            self.backgroundColor = .clear
            let cgColors = colors.map({$0.cgColor})
            if let gradientLayer = self.layer as? CAGradientLayer{
                gradientLayer.colors = cgColors
                gradientLayer.startPoint = startPoint
                gradientLayer.endPoint = endPoint
            }
        }
    
        override class var layerClass: AnyClass {
            return CAGradientLayer.self
        }
    
    }
    

    The second is to use a transform instead of changing the constraints on the container and everything will scale proportionately. I will leave this for you to work it out if that is the way you would like to go.