10

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
  • 11,170
  • 8
  • 62
  • 92
Alexandre
  • 161
  • 1
  • 1
  • 10
  • "It's here where there is a problem" But you have not said what the problem _is_. – matt Nov 03 '16 at 16:52
  • Oh yeah write, hum in fact i have the images that are changing every 5s; but the gradient effect on my UIimageView doesn't "work", i have no shade or something on top of my Images :) – Alexandre Nov 03 '16 at 21:03
  • @Alexandre Have you stored this image in i-Phone's Gallery ? – Khushbu Desai Sep 11 '17 at 06:03

6 Answers6

32

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];
Graham
  • 7,431
  • 18
  • 59
  • 84
Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92
  • @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. – Caleb Kleveter Nov 04 '16 at 12:42
  • @CalebKleveter I have used this code but now I want to save this Image in my Gallery , How do I achieve this ? – Khushbu Desai Sep 11 '17 at 06:02
  • @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. – Caleb Kleveter Sep 11 '17 at 10:57
  • @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. – Khushbu Desai Sep 11 '17 at 11:46
  • 1
    @KhushbuDesai Look at this: https://www.hackingwithswift.com/example-code/media/how-to-render-a-uiview-to-a-uiimage – Caleb Kleveter Sep 11 '17 at 16:55
  • In Swift 4.2 - 'bringSubview(toFront:)' has been renamed to 'bringSubviewToFront(_:)' – brokenrhino Dec 06 '19 at 17:05
  • 1
    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`. – Zhou Haibo Sep 25 '21 at 07:12
16

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.

gandhi Mena
  • 2,115
  • 1
  • 19
  • 20
  • 1
    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! – Bogdan Jul 31 '17 at 13:24
  • 1
    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 – Aakash Dave Oct 15 '17 at 10:16
  • I agree with @eraser2021999, the line defining the frame could be gradient.frame = self.frame instead, and the method should receive no parameter. – Iris Oct 19 '17 at 20:48
  • Hi, i fixed the the problem for fully cover the image. Thanks for your comments. @eraser2021999, Aakash, Iris. – gandhi Mena Nov 04 '17 at 17:09
  • @gandhiMena how did you solve that not fully cover image stuff? – ios_dev Mar 22 '19 at 12:21
  • Don't use self.frame, use CGRect(origin: .zero, size: self.frame.size) – Howard Shere Mar 27 '19 at 13:07
  • I found put the call in `viewDidLoad` will not take effect, instead I put it under `viewDidLayoutSubview` do the things. – Zhou Haibo Sep 25 '21 at 07:21
2

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
Reimond Hill
  • 4,278
  • 40
  • 52
1

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)
    }
}
Viral Savaj
  • 3,379
  • 1
  • 26
  • 39
0
extension UIView {

    func addGradient(frame: CGRect) {
        let gradientView = UIView(frame: self.frame)
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = frame
        gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
        gradientLayer.locations = [0.0, 1.0]
        gradientView.layer.insertSublayer(gradientLayer, at: 0)
        addSubview(gradientView)
    }
}

How to use

let headerView = UIImageView(frame: viewFrame)

// Setup frame
let gradientViewFrame = yourGradientFrame;
headerView.addGradient(frame: gradientViewFrame)
Bhuvan Bhatt
  • 3,276
  • 2
  • 18
  • 23
0

These solutions didn't work properly for me. This answer was inspired from this. I have IBOutlet and it's like,

@IBOutlet weak var imageView: UIImageView!

Then I added this piece of code.

let gradient = CAGradientLayer()
gradient.frame = imageView.bounds
gradient.contents = imageView.image?.cgImage
gradient.colors = [UIColor.green.cgColor, UIColor.blue.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 1)
imageView.layer.addSublayer(gradient)

It works as I wanted. The most important point is adding contents to gradient layer.

Wimukthi Rajapaksha
  • 961
  • 1
  • 11
  • 23