1 // Copyright 2021-present 650 Industries. All rights reserved.
2 
3 import QuartzCore
4 import CoreGraphics
5 
6 var defaultStartPoint = CGPoint(x: 0.5, y: 0.0)
7 var defaultEndPoint = CGPoint(x: 0.5, y: 1.0)
8 var defaultLocations: [CGFloat] = []
9 
10 final class LinearGradientLayer: CALayer {
11   var colors = [CGColor]()
12   var startPoint = defaultStartPoint
13   var endPoint = defaultEndPoint
14   var locations = defaultLocations
15 
16   override init() {
17     super.init()
18     self.needsDisplayOnBoundsChange = true
19     self.masksToBounds = true
20   }
21 
22   override init(layer: Any) {
23     super.init(layer: layer)
24     self.needsDisplayOnBoundsChange = true
25     self.masksToBounds = true
26   }
27 
28   required init?(coder: NSCoder) {
29     fatalError("init(coder:) has not been implemented")
30   }
31 
setColorsnull32   func setColors(_ colors: [CGColor]) {
33     self.colors = colors
34     setNeedsDisplay()
35   }
36 
setStartPointnull37   func setStartPoint(_ startPoint: CGPoint?) {
38     self.startPoint = startPoint ?? defaultStartPoint
39     setNeedsDisplay()
40   }
41 
setEndPointnull42   func setEndPoint(_ endPoint: CGPoint?) {
43     self.endPoint = endPoint ?? defaultEndPoint
44     setNeedsDisplay()
45   }
46 
setLocationsnull47   func setLocations(_ locations: [CGFloat]?) {
48     self.locations = locations ?? defaultLocations
49     setNeedsDisplay()
50   }
51 
displaynull52   override func display() {
53     super.display()
54 
55     if colors.isEmpty || bounds.size.width.isZero || bounds.size.height.isZero {
56       return
57     }
58     let hasAlpha = colors.reduce(false) { result, color in
59       return result || color.alpha < 1.0
60     }
61 
62     UIGraphicsBeginImageContextWithOptions(bounds.size, !hasAlpha, 0.0)
63 
64     guard let contextRef = UIGraphicsGetCurrentContext() else {
65       return
66     }
67 
68     draw(in: contextRef)
69 
70     guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
71       return
72     }
73 
74     self.contents = image.cgImage
75     self.contentsScale = image.scale
76 
77     UIGraphicsEndImageContext()
78   }
79 
drawnull80   override func draw(in ctx: CGContext) {
81     super.draw(in: ctx)
82 
83     ctx.saveGState()
84 
85     let colorSpace = CGColorSpaceCreateDeviceRGB()
86     let locations = colors.enumerated().map { (offset: Int, _: CGColor) -> CGFloat in
87       if self.locations.count > offset {
88         return self.locations[offset]
89       } else {
90         return CGFloat(offset) / CGFloat(colors.count - 1)
91       }
92     }
93 
94     if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations) {
95       let size = bounds.size
96 
97       ctx.drawLinearGradient(
98         gradient,
99         start: CGPoint(x: startPoint.x * size.width, y: startPoint.y * size.height),
100         end: CGPoint(x: endPoint.x * size.width, y: endPoint.y * size.height),
101         options: [.drawsBeforeStartLocation, .drawsAfterEndLocation]
102       )
103     }
104 
105     ctx.restoreGState()
106   }
107 }
108