Search code examples
iosuicolorcagradientlayer

iOS Programmatically Gradient


How can I achieve a gradient exactly same as in attached image programmatically?Screenshot


Solution

  • The answer largely depends on what the designer used to create the gradient. There are some tools such as Invision that provide helpful information to the developer, such as gradient colours and points. If they just created it in Photoshop, however, it's going to be hard to reproduce programmatically, and maybe you should just ask for an image of the gradient.

    Another solution would be to use an @IBDesignable gradient view in Xcode; many developers have created them, including me, largely as a result of having to deal with this problem a lot in the past. A major upside of this approach is that it's super-easy to tweak the colours, and even to animate it if you need to.

    I have an open-source example available on GitHub, plus I wrote an extended explanation of how it works.

    I had a quick attempt to make a gradient like yours using my project, and this is how it looks in Xcode:-

    enter image description here

    The crucial part is the top of the Attributes Inspector panel, where you can easily set the start and end gradient colours, and also the gradient angle - yours looks angled around 310 degrees.

    enter image description here

    The main piece of code you need is pasted below. Follow the links above for a walkthrough of how it works.

    import UIKit
    @IBDesignable
    class LDGradientView: UIView {
    
        // the gradient start colour
        @IBInspectable var startColor: UIColor? {
            didSet {
                updateGradient()
            }
        }
    
        // the gradient end colour
        @IBInspectable var endColor: UIColor? {
            didSet {
                updateGradient()
            }
        }
    
        // the gradient angle, in degrees anticlockwise from 0 (east/right)
        @IBInspectable var angle: CGFloat = 270 {
            didSet {
                updateGradient()
            }
        }
    
        // the gradient layer
        private var gradient: CAGradientLayer?
    
        // initializers
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            installGradient()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            installGradient()
        }
    
        // Create a gradient and install it on the layer
        private func installGradient() {
            // if there's already a gradient installed on the layer, remove it
            if let gradient = self.gradient {
                gradient.removeFromSuperlayer()
            }
            let gradient = createGradient()
            self.layer.addSublayer(gradient)
            self.gradient = gradient
        }
    
        // Update an existing gradient
        private func updateGradient() {
            if let gradient = self.gradient {
                let startColor = self.startColor ?? UIColor.clear
                let endColor = self.endColor ?? UIColor.clear
                gradient.colors = [startColor.cgColor, endColor.cgColor]
                let (start, end) = gradientPointsForAngle(self.angle)
                gradient.startPoint = start
                gradient.endPoint = end
            }
        }
    
        // create gradient layer
        private func createGradient() -> CAGradientLayer {
            let gradient = CAGradientLayer()
            gradient.frame = self.bounds
            return gradient
        }
    
        // create vector pointing in direction of angle
        private func gradientPointsForAngle(_ angle: CGFloat) -> (CGPoint, CGPoint) {
            // get vector start and end points
            let end = pointForAngle(angle)
            //let start = pointForAngle(angle+180.0)
            let start = oppositePoint(end)
            // convert to gradient space
            let p0 = transformToGradientSpace(start)
            let p1 = transformToGradientSpace(end)
            return (p0, p1)
        }
    
        // get a point corresponding to the angle
        private func pointForAngle(_ angle: CGFloat) -> CGPoint {
            // convert degrees to radians
            let radians = angle * .pi / 180.0
            var x = cos(radians)
            var y = sin(radians)
            // (x,y) is in terms unit circle. Extrapolate to unit square to get full vector length
            if (fabs(x) > fabs(y)) {
                // extrapolate x to unit length
                x = x > 0 ? 1 : -1
                y = x * tan(radians)
            } else {
                // extrapolate y to unit length
                y = y > 0 ? 1 : -1
                x = y / tan(radians)
            }
            return CGPoint(x: x, y: y)
        }
    
        // transform point in unit space to gradient space
        private func transformToGradientSpace(_ point: CGPoint) -> CGPoint {
            // input point is in signed unit space: (-1,-1) to (1,1)
            // convert to gradient space: (0,0) to (1,1), with flipped Y axis
            return CGPoint(x: (point.x + 1) * 0.5, y: 1.0 - (point.y + 1) * 0.5)
        }
    
        // return the opposite point in the signed unit square
        private func oppositePoint(_ point: CGPoint) -> CGPoint {
            return CGPoint(x: -point.x, y: -point.y)
        }
    
        // ensure the gradient gets initialized when the view is created in IB
        override func prepareForInterfaceBuilder() {
            super.prepareForInterfaceBuilder()
            installGradient()
            updateGradient()
        }
    }