Search code examples
iosswiftuiviewcagradientlayeribdesignable

Gradient not filling IBDesignable UIView


I'm attempting to add a gradient on an @IBDesignable UIView. All was well in Simulator, but when I run it on a physical device, the gradient doesn't fill the screen. I've got my gradient code in a UIView extension:

extension UIView {
    func gradientFrom(_ colors: [UIColor]) {
        let cgColors = colors.map({return $0.cgColor})
        let gradient = CAGradientLayer()
        gradient.frame = self.bounds
        gradient.locations =  [0.0, 1.0]
        gradient.colors = cgColors
        self.layer.insertSublayer(gradient, at: 0)
    }
}

Next, my gradient-centric code from my UIView:

@IBDesignable class MyCustomView: UIView {

    var view = UIView()

    // MARK: - IBOutlets
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textView: UITextView!

    fileprivate var gradientColors: [UIColor] = []

    @IBInspectable var gradientColor0: UIColor = UIColor.flatWhite {
        didSet {
            gradientColors.remove(at: 0)
            gradientColors.insert(gradientColor0, at: 0)
        }
    }

    @IBInspectable var gradientColor1: UIColor = UIColor.flatWhiteDark {
        didSet {
            gradientColors.remove(at: 1)
            gradientColors.insert(gradientColor1, at: 1)
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        xibSetup()
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        xibSetup()
        setup()
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setup()
    }

    func setup() {
        textView.scrollRangeToVisible(NSMakeRange(0, 0))
        [gradientColor0, gradientColor1].forEach({gradientColors.append($0)})
        view.gradientFrom(gradientColors)
        label.textColor = labelFontColor
        label.backgroundColor = UIColor.clear
        textView.backgroundColor = UIColor.clear
        textView.textColor = textViewFontColor
    }

    // MARK: - Nib handlers

    fileprivate func xibSetup() {
        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(view)
    }

    fileprivate func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "AttributionView", bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
        return view
    }

    public func applyBackgroundGradient() {        
        view.gradientFrom(gradientColors)
    }

    func resizeView() {
        textView.sizeToFit()
        textView.scrollRangeToVisible(NSMakeRange(0, 0))
    }
}

On MyViewController, I add a UIView, set its subclass to MyCustomView. Interface builder populates, but when I go to run it on a physical device, the gradient in MyCustomView does not fill the entire screen.

I tried running applyBackgroundGradient from MyViewController's viewDidLoad, viewDidLayoutSubviews, viewDidAppear, none of those seem to get the job done.

Any suggestions re: how to fix this are greatly appreciated. Thank you for reading.


Solution

  • try to replace

    gradient.frame = self.bounds
    

    with

    gradient.frame.size = self.frame.size
    gradient.frame.origin = CGPoint(x: 0.0, y: 0.0)
    

    and you definitely should call it like

    var didLayoutSubviews = false
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if !self.didLayoutSubviews {
             self.didLayoutSubviews = true
             //apply gradient
             //you can also try to execute drawing inside `DispatchQueue.main.async {}`
        }
    }
    

    Addendum from OP:

    Part of my problem derived from my didSet calls on the colors for the gradient. Instead of mucking around setting up the array in didSet, I moved the code for populating the gradientColors array down to the applyBackgroundGradient method.

    @IBInspectable var gradientColor0: UIColor = UIColor.flatWhite {
        didSet {
            applyBackgroundGradient()
        }
    }
    
    @IBInspectable var gradientColor1: UIColor = UIColor.flatWhiteDark {
        didSet {
            applyBackgroundGradient()
        }
    }
    

    ...and the gradient code on MyCustomView looks like this:

    public func applyBackgroundGradient() {
        gradientColors = [gradientColor0, gradientColor1] 
        self.view.gradientFrom(gradientColors)
    }