Search code examples
iosswiftuiimageviewuilabelgradient

Add Gradient Layer to UIImageView in IOS thru CAGradientLayer


I am trying to add a Gradient in IOS like I did in Android. So I can see my label on top of the UIImageView and its not hidden.

enter image description here

In android I did this in a drawable `

<gradient
    android:angle="90"
    android:endColor="#00ffffff"
    android:startColor="#aa000000"
    android:centerColor="#00ffffff" />

<corners android:radius="0dp" />

`

I am trying to do this in IOS Swift 4.2 and I get the following :

    let gradient: CAGradientLayer = CAGradientLayer()
    gradient.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
    gradient.locations = [0.0 , 1.0]
    gradient.startPoint = CGPoint(x: 0.0, y: 1.0)
    gradient.endPoint = CGPoint(x: 0.5, y: 0.5)
    gradient.frame = CGRect(x: 0.0, y: 0.0, width: showImageView.frame.size.width, height: showImageView.frame.size.height)
    showImageView.layer.addSublayer(gradient)

enter image description here

How do I get the Gradient to start black from the bottom and go up in 90 degrees?How do I change the opacity?Any idea?


Solution

  • A few thoughts:

    1. Choose colors with alpha less than 1. Perhaps:

      gradient.colors = [
          UIColor.black.withAlphaComponent(0.8).cgColor,
          UIColor.black.withAlphaComponent(0).cgColor
      ]
      
    2. To have this gradient go vertically, choose start and end points that have the same x value. E.g. to cover bottom half, perhaps:

      gradient.startPoint = CGPoint(x: 0.5, y: 1)
      gradient.endPoint = CGPoint(x: 0.5, y: 0.5)
      
    3. Be very careful about using frame. You want the layer to reference the bounds of the image view (using the image view’s coordinate system), not the frame (which specifies where the image view is in its superview’s coordinate system). If your image view happens to be at 0, 0, you might not see a difference, but if you move the image view around at all, these will start to deviate. So, assuming you’re adding this gradient to the image view, itself, you’d use the image view’s bounds:

      gradient.frame = showImageView.bounds
      
    4. Be aware that the frame/bounds of the image view can sometimes change. So, we will implement layoutSubviews in our UIImageView or UITableViewCell subclass, and update the gradient’s frame there, e.g.

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

      That way it will update the gradient’s frame when the view’s layout changes.

      The other solution is to define a UIView subclass, say GradientView, that renders the CAGradientLayer and define the base layerClass of that view to be a CAGradientLayer. Then add this view in between the image view and the label, define its constraints, and then you’ll have a gradient that changes dynamically as the GradientView size changes. (This layerClass approach has the advantage that it yields better animation/transitions than you’d get by just updating the frame programmatically.)

      Thus, something like:

      class GradientView: UIView {
          override class var layerClass: AnyClass { return CAGradientLayer.self }
          var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
      
          var firstColor: UIColor = UIColor.black.withAlphaComponent(0.8) {
              didSet { updateColors() }
          }
      
          var secondColor: UIColor = UIColor.black.withAlphaComponent(0) {
              didSet { updateColors() }
          }
      
          override init(frame: CGRect = .zero) {
              super.init(frame: frame)
              configure()
          }
      
          required init?(coder: NSCoder) {
              super.init(coder: coder)
              configure()
          }
      }
      
      private extension GradientView {
          func configure() {
              updateColors()
              gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
              gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.5)
          }
      
          func updateColors() {
              gradientLayer.colors = [firstColor, secondColor].map { $0.cgColor }
          }
      }
      
    5. If you really want your white text to pop, in addition to adding gradient over the image, you might also add a black glow/shadow to the text. It’s subtle, but really makes the text pop. Just make its shadow color the same color as the gradient.

      So you can see the effect, here are four renditions of a cell, with (a) no gradient; (b) with gradient; (c) with gradient and nice gaussian blur around text; and (d) with simple shadow around text:

      enter image description here

      The nice, gaussian blur around the text is rendered with:

      customLabel.layer.shadowColor = UIColor.black.cgColor
      customLabel.layer.shadowRadius = 3
      customLabel.layer.shadowOpacity = 1
      customLabel.layer.masksToBounds = false
      customLabel.layer.shouldRasterize = true
      

      I think that third rendition (with gradient over the image, with glow around the text) is best. It’s subtle, but the text really pops. But gaussian blurs are computationally expensive, so if you find this adversely affects your performance too much, you can use the fourth option, with the simple, non-blurred shadow:

      customLabel.shadowColor = .black
      
      // perhaps also
      // customLabel.shadowOffset = CGSize(width: -1, height: -1)