jack
jack

Reputation: 77

gradient in for multipel UIBezierPath

I'm supposed to create this.

this

I did search the google, youtube, and StackOverflow, and the code below is the result of my research.

     @IBDesignable class TriangleView2: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    let gradient = CAGradientLayer()
    override func draw(_ rect: CGRect) {
 
        //draw the line of UIBezierPath
        
        let path1 = UIBezierPath()
        path1.move(to: CGPoint(x: rect.minX - 100, y: rect.maxY - 80))
        path1.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        path1.addLine(to: CGPoint(x: (rect.maxX + 90  ), y: rect.minY/2 ))
        path1.close()

        // add clipping path. this draws an imaginary line (to create bounds) from the
        //ends of the UIBezierPath line down to the bottom of the screen
        let clippingPath = path1.copy() as! UIBezierPath
        clippingPath.move(to: CGPoint(x: rect.minX - 100, y: rect.maxY - 80))
        clippingPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        clippingPath.addLine(to: CGPoint(x: (rect.maxX + 90  ), y: rect.minY/2 ))
        clippingPath.close()
        
        clippingPath.addClip()
        
        // create and add the gradient
        let colors = [theme.current.profile_start_view1.cgColor, theme.current.profile_end_view1.cgColor]
        
        let colorSpace = CGColorSpaceCreateDeviceRGB()        
        let colorLocations:[CGFloat] = [0.0, 1.0]        
        let gradient = CGGradient(colorsSpace: colorSpace,
                                  colors: colors as CFArray,
                                  locations: colorLocations)
        
        let context = UIGraphicsGetCurrentContext()
        let startPoint = CGPoint(x: 1, y: 1)
        let endPoint = CGPoint(x: 1, y: bounds.maxY)
        // and lastly, draw the gradient.
        context!.drawLinearGradient(gradient!, start: startPoint, end: 
      endPoint, options: CGGradientDrawingOptions.drawsAfterEndLocation)
        }
    }

Right not I have 2 views ( will be 3 if I could complete it) with some differences.

The result is this.

this

These 2 views do not have the same colour, but as you can see both views have the same gradient with the same direction.

Does anyone have any suggestion?

Upvotes: 1

Views: 783

Answers (3)

rmaddy
rmaddy

Reputation: 318794

This is somewhat similar to Codo's answer but you only need 4 points.

class FourGradientsView: UIView {
    override func draw(_ rect: CGRect) {
        let ctx = UIGraphicsGetCurrentContext()!

        // Points of area to draw - adjust these 4 variables as needed
        let tl = CGPoint(x: 0, y: 0)
        let tr = CGPoint(x: bounds.width * 1.3, y: 0)
        let bl = CGPoint(x: -bounds.width * 1.8, y: bounds.height * 1.4)
        let br = CGPoint(x: bounds.width * 1.3, y: bounds.height * 2)

        // Find the intersection of the two crossing diagonals
        let s1x = br.x - tl.x
        let s1y = br.y - tl.y
        let s2x = tr.x - bl.x
        let s2y = tr.y - bl.y
        //let s = (-s1y * (tl.x - bl.x) + s1x * (tl.y - bl.y)) / (-s2x * s1y + s1x * s2y)
        let t = ( s2x * (tl.y - bl.y) - s2y * (tl.x - bl.x)) / (-s2x * s1y + s1x * s2y)
        let center = CGPoint(x: tl.x + (t * s1x), y: tl.y + (t * s1y))

        // Create clipping region to avoid drawing where we don't want any gradients
        ctx.saveGState()
        let clip = CGPoint(x: 0, y: bounds.height * 0.7)
        let clipPath = UIBezierPath()
        clipPath.move(to: CGPoint(x: 0, y: 0))
        clipPath.addLine(to: clip)
        clipPath.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
        clipPath.addLine(to: CGPoint(x: bounds.width, y: 0))
        clipPath.close()
        clipPath.addClip()

        // Use these two colors for all 4 gradients (adjust as needed)
        let colors = [
            UIColor(hue: 120/360, saturation: 1, brightness: 0.85, alpha: 1).cgColor,
            UIColor(hue: 120/360, saturation: 1, brightness: 0.3, alpha: 1).cgColor
        ] as CFArray

        // The common gradient
        let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors, locations: nil)!

        // Top gradient
        ctx.saveGState()
        let pathTop = UIBezierPath()
        pathTop.move(to: tl)
        pathTop.addLine(to: tr)
        pathTop.addLine(to: center)
        pathTop.close()
        pathTop.addClip()

        ctx.drawLinearGradient(gradient, start: CGPoint(x: bounds.width, y: 0), end: CGPoint(x: 0, y: 0), options: [])
        ctx.restoreGState()

        // Right gradient
        ctx.saveGState()
        let pathRight = UIBezierPath()
        pathRight.move(to: tr)
        pathRight.addLine(to: br)
        pathRight.addLine(to: center)
        pathRight.close()
        pathRight.addClip()

        ctx.drawLinearGradient(gradient, start: CGPoint(x: bounds.width, y: bounds.height), end: CGPoint(x: bounds.width, y: 0), options: [])
        ctx.restoreGState()

        // Bottom gradient
        ctx.saveGState()
        let pathBottom = UIBezierPath()
        pathBottom.move(to: br)
        pathBottom.addLine(to: bl)
        pathBottom.addLine(to: center)
        pathBottom.close()
        pathBottom.addClip()

        ctx.drawLinearGradient(gradient, start: CGPoint(x: 0, y: bounds.height), end: CGPoint(x: bounds.width, y: bounds.height), options: [])
        ctx.restoreGState()

        // Left gradient
        ctx.saveGState()
        let pathLeft = UIBezierPath()
        pathLeft.move(to: tl)
        pathLeft.addLine(to: bl)
        pathLeft.addLine(to: center)
        pathLeft.close()
        pathLeft.addClip()

        ctx.drawLinearGradient(gradient, start: CGPoint(x: 0, y: 0), end: CGPoint(x: 0, y: bounds.height), options: [])
        ctx.restoreGState()

        ctx.restoreGState()
    }
}

let grView = FourGradientsView(frame: CGRect(x: 0, y: 0, width: 320, height: 320))
grView.backgroundColor = .black

Upvotes: 1

Codo
Codo

Reputation: 78825

Here's an example that you can run directly in the playground.

As you want four gradients and as gradients are draw using clipping, the graphics context is saved and restored several times (to reset the clipping).

The grading start and end point is one of the clipping corners. That's not needed. You can (and probably) should use separate points. To achieve the desired result, you sometimes want to use a start or end point considerably outside the the clipping area.

import UIKit
import PlaygroundSupport


class TriangleView2: UIView {

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

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

    override func draw(_ rect: CGRect) {

        let colors = [UIColor(red: 50/255.0, green: 242/255.0, blue: 111/255.0, alpha: 1).cgColor,
                      UIColor(red: 29/255.0, green: 127/255.0, blue: 60/255.0, alpha: 1).cgColor]
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let colorLocations:[CGFloat] = [0.0, 1.0]
        let gradient = CGGradient(colorsSpace: colorSpace,
                                  colors: colors as CFArray,
                                  locations: colorLocations)!
        let options: CGGradientDrawingOptions = [CGGradientDrawingOptions.drawsBeforeStartLocation, CGGradientDrawingOptions.drawsAfterEndLocation]

        let p1 = CGPoint(x: 0, y: 0)
        let p2 = CGPoint(x: bounds.width, y: 0)
        let p3 = CGPoint(x: bounds.width, y: 20)
        let p4 = CGPoint(x: bounds.width / 3, y: 140)
        let p5 = CGPoint(x: 0, y: 200)
        let p6 = CGPoint(x: bounds.width * 5 / 8, y: 260)
        let p7 = CGPoint(x: 0, y: 230)
        let p8 = CGPoint(x: bounds.width, y: 280)

        let context = UIGraphicsGetCurrentContext()!

        context.saveGState()
        let path1 = UIBezierPath()
        path1.move(to: p1)
        path1.addLine(to: p2)
        path1.addLine(to: p3)
        path1.addLine(to: p4)
        path1.close()
        path1.addClip()
        context.drawLinearGradient(gradient, start: p3, end: p1, options: options)
        context.restoreGState()

        context.saveGState()
        let path2 = UIBezierPath()
        path2.move(to: p1)
        path2.addLine(to: p4)
        path2.addLine(to: p5)
        path2.close()
        path2.addClip()
        context.drawLinearGradient(gradient, start: p1, end: p5, options: options)
        context.restoreGState()

        context.saveGState()
        let path3 = UIBezierPath()
        path3.move(to: p3)
        path3.addLine(to: p8)
        path3.addLine(to: p6)
        path3.addLine(to: p4)
        path3.close()
        path3.addClip()
        context.drawLinearGradient(gradient, start: p8, end: p3, options: options)
        context.restoreGState()

        context.saveGState()
        let path4 = UIBezierPath()
        path4.move(to: p5)
        path4.addLine(to: p4)
        path4.addLine(to: p6)
        path4.addLine(to: p7)
        path4.close()
        path4.addClip()
        context.drawLinearGradient(gradient, start: p7, end: p6, options: options)
        context.restoreGState()
    }
}

let main = TriangleView2(frame: CGRect(x: 0, y: 0, width: 320, height: 500))
PlaygroundPage.current.liveView = main

Update

One more thing: Do not use the rect parameter to derive the geometry of your shapes. rect does not refer to the view size or position. Instead, it's area that needs to be redrawn. If iOS decides only part of your view needs to be redrawn, your code will draw the wrong shape.

Upvotes: 0

Duncan C
Duncan C

Reputation: 131418

You wrote code that always uses the same start and end color, always uses the same color locations, and always uses the same start and end points. Of course the gradients have the same gradient with the same direction.

Give your views gradient start point and end point properties as well as start and end colors. Set the gradient start points for your gradient views in your view controller's layoutDidChange() method, based on the bounds of the views. (That way you handle device rotation and different sized devices correctly.

Upvotes: 0

Related Questions