Add a gradient on UIImageView

20,008

Solution 1

I would suggest putting a UIView with the gradient on top of the UIImageView:

@IBOutlet weak var shanghaiImage: UIImageView!

let view = UIView(frame: profileImageView.frame)

let gradient = CAGradientLayer()

gradient.frame = view.frame

gradient.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]

gradient.locations = [0.0, 1.0]

view.layer.insertSublayer(gradient, at: 0)

shanghaiImage.addSubview(view)

shanghaiImage.bringSubview(toFront: view)

Objective-C:

UIView *view = [[UIView alloc] initWithFrame: profileImageView.frame];

CAGradientLayer *gradient = [[CAGradientLayer alloc] init];

gradient.frame = view.frame;

gradient.colors = @[ (id)[[UIColor clearColor] CGColor], (id)[[UIColor blackColor] CGColor] ];

gradient.locations = @[@0.0, @1.0];

[view.layer insertSublayer: gradient atIndex: 0];

[shanghaiImage addSubview: view];

[shanghaiImage bringSubviewToFront: view];

Solution 2

You can use extension for swift 3, swift 4 and swift 5

Create a new file for your extension of UIImageView like UIImageView_extension.swift and set into this code:

UIImageView is extends UIView so if you change UIImageView to UIView then it become more dynamic i.e can used by all components who extents UIView. So I used UIView instead of UIImageView.

import UIKit

extension UIView{
   // For insert layer in Foreground
   func addBlackGradientLayerInForeground(frame: CGRect, colors:[UIColor]){
    let gradient = CAGradientLayer()
    gradient.frame = frame
    gradient.colors = colors.map{$0.cgColor}
    self.layer.addSublayer(gradient)
   }
   // For insert layer in background
   func addBlackGradientLayerInBackground(frame: CGRect, colors:[UIColor]){
    let gradient = CAGradientLayer()
    gradient.frame = frame
    gradient.colors = colors.map{$0.cgColor}
    self.layer.insertSublayer(gradient, at: 0)
   }
}

and in your ViewController.swift you can use it:

class myViewController: UIViewController{
    @IBOutlet weak var imageView: UIImageView!

    override func viewDidLoad() {
    super.viewDidLoad()

        imageView.addBlackGradientLayerInBackground(frame: view.bounds, colors:[.clear, .black])
        //Alternative
        //imageView.addBlackGradientLayerInBackground(frame: imageView.frame, colors: [.clear, .black])
    }
}

This function needs a frame, so you only need a frame from view or yourself imageView. Always think like generic function, then you can change the gradient colors without troubles in the future in other views.

Solution 3

In Swift 5.1 I have create a custom class that inherits from UIImageView and supports multiple gradient configurations.

import UIKit
import SnapKit

class GradientImageView: UIImageView {

    //MARK: - View model
    enum GradientDirection {
        case upDown
        case downUp
        case leftRight
        case rightLeft
        case topLeftBottomRight
        case topRightBottomLeft
        case bottomLeftTopRight
        case bottomRightTopLeft
    }
    
    //MARK: - Properties
    var colors: [UIColor] = [] {
        didSet {
            updateGradient()
        }
    }
    private var cgColors: [CGColor] {
        return colors.map({ $0.cgColor })
    }
    var gradientDirection: GradientDirection = .downUp {
        didSet {
            updateGradient()
        }
    }
    private lazy var gradientLayer: CAGradientLayer = {
        let layer = CAGradientLayer()
    
        layer.shouldRasterize = true
        
        return layer
    }()
    
    
    //MARK: UI
    private lazy var overlayView: UIView = { return UIView() }()
    
    
    //MARK: - Constructor
    init(colors: [UIColor], gradientDirection: GradientDirection) {
        super.init(frame: .zero)
        
        self.colors = colors
        self.gradientDirection = gradientDirection
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}


//MARK: - Lifecycle methods methods
extension GradientImageView {
    
    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        
        if superview != nil {
            setupUI()
            updateGradient()
        }
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        gradientLayer.frame = overlayView.frame
    
    }
    
}


//MARK: - Private methods
private extension GradientImageView {
    
    func setupUI() {
        addSubview(overlayView)
        //With Snapkit
        overlayView.snp.makeConstraints { (maker) in
            maker.edges.equalToSuperview()
        }

        //Without Snapkit
        //overlayView.translatesAutoresizingMaskIntoConstraints = false
        //overlayView.topAnchor.constraint(equalTo: overlayView.superview!.topAnchor).isActive = true
        //overlayView.leftAnchor.constraint(equalTo: overlayView.superview!.leftAnchor).isActive = true
        //overlayView.bottomAnchor.constraint(equalTo: overlayView.superview!.bottomAnchor).isActive = true
        //overlayView.rightAnchor.constraint(equalTo: overlayView.superview!.rightAnchor).isActive = true

        overlayView.layer.addSublayer(gradientLayer)
    }
    
    func updateGradient() {
        
        gradientLayer.colors = cgColors
        
        switch gradientDirection {
        case .upDown:
            gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
            gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
        case .downUp:
            gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
            gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
        case .leftRight:
            gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
            gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
        case .rightLeft:
            gradientLayer.startPoint = CGPoint(x: 1, y: 0.5)
            gradientLayer.endPoint = CGPoint(x: 0, y: 0.5)
        case .topLeftBottomRight:
            gradientLayer.startPoint = CGPoint(x: 0, y: 0)
            gradientLayer.endPoint = CGPoint(x: 1, y: 1)
        case .topRightBottomLeft:
            gradientLayer.startPoint = CGPoint(x: 1, y: 0)
            gradientLayer.endPoint = CGPoint(x: 0, y: 1)
        case .bottomLeftTopRight:
            gradientLayer.startPoint = CGPoint(x: 0, y: 1)
            gradientLayer.endPoint = CGPoint(x: 1, y: 0)
        case .bottomRightTopLeft:
            gradientLayer.startPoint = CGPoint(x: 1, y: 1)
            gradientLayer.endPoint = CGPoint(x: 0, y: 0)
        }
        
    }
    
}

USAGE

let gradientImageView = GradientImageView(colors: [YOUR COLORS], gradientDirection: .upDown)
gradientImageView.image = //YOUR Image

Solution 4

That should be the correct answer

extension UIImageView {

func makeGradient() {
    let gradient = CAGradientLayer()
    gradient.frame = self.bounds
    gradient.contents = self.image?.cgImage
    gradient.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
    gradient.startPoint = CGPoint(x: 0.5, y: 0.5)
    gradient.endPoint = CGPoint(x: 0.5, y: 1)
    self.layer.addSublayer(gradient)
}

}

Share:
20,008
Alexandre
Author by

Alexandre

Updated on July 09, 2022

Comments

  • Alexandre
    Alexandre almost 2 years

    I am trying to add a sublayer on my UIImageView but it doesn't work.

    • I have a set of 10 images named from photo0 to photo9 and I display it with a timer of 5s.
    • The outlet shanghaiImage is my background

    I would like to add a gradient on top of this marty like: transparent (top) to black (bottom).

    Thanks for the help :)

    Here is my code in Swift 3.

    This part is fine :

    import UIKit
    
    class ViewController: UIViewController {
    
    @IBOutlet weak var shanghaiImage: UIImageView!
    
    // beginning index
    var _curentImageIndex:Int = 0
    // number of images
    let NUMBER_OF_IMAGES:Int = 10
    // creation of the Timer
    var _uiTimer:Timer?
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        showPhoto(atIndex: _curentImageIndex)
    }
    
    // MARK TIMER ---------
    
    func selectNewTimer(){
        if let existingTimer:Timer = _uiTimer{
            existingTimer.invalidate()
        }
        _uiTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(ViewController.showNextImage), userInfo: nil, repeats: true)
    }
    

    It's here where there is a problem. I don't know why it's not working.

    // MARK PHOTO ---------
    func showPhoto(atIndex index:Int){
    
        let photoName:String =  "photo\(index)"
        shanghaiImage.image =  UIImage(named: photoName)
    
        let gradient = CAGradientLayer()
        gradient.frame = shanghaiImage.bounds
        let startColor = UIColor(colorLiteralRed: 0, green: 0, blue: 0, alpha: 1)
        let endColor = UIColor.black
    
        gradient.colors = [startColor, endColor]
        shanghaiImage.layer.insertSublayer(gradient, at: 0)
    
    
        _curentImageIndex  =  index
        selectNewTimer()
        }
    
    func showNextImage() {
        var nextPhotoIndex:Int = _curentImageIndex + 1
            if nextPhotoIndex >= NUMBER_OF_IMAGES {
                nextPhotoIndex = 0
            }
        showPhoto(atIndex: nextPhotoIndex)
    }
    
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    }
    
  • Caleb Kleveter
    Caleb Kleveter over 7 years
    @DeepakKumar I updated my answer to have objective-c in it. My Obj-C is a bit rusty and Xcode is out of commission right now, so it might have some bugs.
  • Bogdan
    Bogdan over 6 years
    Since this is an extension for UIImageView instead of adding the parameter frame, you can use self.frame property. The code should be something like: gradient.frame = self.frame. Anyway, thanks for the solution, it really works!
  • Khushbu Desai
    Khushbu Desai over 6 years
    @CalebKleveter I have used this code but now I want to save this Image in my Gallery , How do I achieve this ?
  • Caleb Kleveter
    Caleb Kleveter over 6 years
    @KhushbuDesai If you want to save an image that you added a gradient to, it will be much more involved because you will have to edit the actual image itself. I would suggest Googling around to try to find something.
  • Khushbu Desai
    Khushbu Desai over 6 years
    @CalebKleveter I had already google it but that's only solution I found is ScreenShot of that image and its not worth it . that's why i am asking you here.
  • Caleb Kleveter
    Caleb Kleveter over 6 years
    @KhushbuDesai Look at this: hackingwithswift.com/example-code/media/…
  • Aakash Dave
    Aakash Dave over 6 years
    Hey, I tried this, works well. The only things I am facing problem with this is that the gradient doesnt fully cover my image, It leaves a bit of image from the right side
  • Iris
    Iris over 6 years
    I agree with @eraser2021999, the line defining the frame could be gradient.frame = self.frame instead, and the method should receive no parameter.
  • gandhi Mena
    gandhi Mena over 6 years
    Hi, i fixed the the problem for fully cover the image. Thanks for your comments. @eraser2021999, Aakash, Iris.
  • ios_dev
    ios_dev about 5 years
    @gandhiMena how did you solve that not fully cover image stuff?
  • Howard Shere
    Howard Shere about 5 years
    Don't use self.frame, use CGRect(origin: .zero, size: self.frame.size)
  • brokenrhino
    brokenrhino over 4 years
    In Swift 4.2 - 'bringSubview(toFront:)' has been renamed to 'bringSubviewToFront(_:)'
  • Jignesh Kanani
    Jignesh Kanani almost 4 years
    How can i add snapit?
  • Reimond Hill
    Reimond Hill almost 4 years
    github.com/SnapKit/SnapKit But I have added without
  • Mert Köksal
    Mert Köksal over 3 years
    I have tried this. It adds a gradient but the image is blank.
  • ChuckZHB
    ChuckZHB over 2 years
    I think this line shoud be let view = UIView(frame: profileImageView.bounds). Use profileImageView.bounds instead of profileImageView.frame, otherwise this gradient view will shift off its parentView shanghaiImage.
  • ChuckZHB
    ChuckZHB over 2 years
    I found put the call in viewDidLoad will not take effect, instead I put it under viewDidLayoutSubview do the things.