1 | #import "BVLinearGradientLayer.h"
|
2 |
|
3 | #include <math.h>
|
4 | #import <UIKit/UIKit.h>
|
5 |
|
6 | @implementation BVLinearGradientLayer
|
7 |
|
8 | - (instancetype)init
|
9 | {
|
10 | self = [super init];
|
11 |
|
12 | if (self)
|
13 | {
|
14 | self.needsDisplayOnBoundsChange = YES;
|
15 | self.masksToBounds = YES;
|
16 | _startPoint = CGPointMake(0.5, 0.0);
|
17 | _endPoint = CGPointMake(0.5, 1.0);
|
18 | _angleCenter = CGPointMake(0.5, 0.5);
|
19 | _angle = 45.0;
|
20 | }
|
21 |
|
22 | return self;
|
23 | }
|
24 |
|
25 | - (void)setColors:(NSArray<id> *)colors
|
26 | {
|
27 | _colors = colors;
|
28 | [self setNeedsDisplay];
|
29 | }
|
30 |
|
31 | - (void)setLocations:(NSArray<NSNumber *> *)locations
|
32 | {
|
33 | _locations = locations;
|
34 | [self setNeedsDisplay];
|
35 | }
|
36 |
|
37 | - (void)setStartPoint:(CGPoint)startPoint
|
38 | {
|
39 | _startPoint = startPoint;
|
40 | [self setNeedsDisplay];
|
41 | }
|
42 |
|
43 | - (void)setEndPoint:(CGPoint)endPoint
|
44 | {
|
45 | _endPoint = endPoint;
|
46 | [self setNeedsDisplay];
|
47 | }
|
48 |
|
49 | - (void)display {
|
50 | [super display];
|
51 |
|
52 | BOOL hasAlpha = NO;
|
53 |
|
54 | for (NSInteger i = 0; i < self.colors.count; i++) {
|
55 | hasAlpha = hasAlpha || CGColorGetAlpha(self.colors[i].CGColor) < 1.0;
|
56 | }
|
57 |
|
58 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, !hasAlpha, 0.0);
|
59 | CGContextRef ref = UIGraphicsGetCurrentContext();
|
60 | [self drawInContext:ref];
|
61 |
|
62 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
63 | self.contents = (__bridge id _Nullable)(image.CGImage);
|
64 | self.contentsScale = image.scale;
|
65 |
|
66 | UIGraphicsEndImageContext();
|
67 | }
|
68 |
|
69 | - (void)setUseAngle:(BOOL)useAngle
|
70 | {
|
71 | _useAngle = useAngle;
|
72 | [self setNeedsDisplay];
|
73 | }
|
74 |
|
75 | - (void)setAngleCenter:(CGPoint)angleCenter
|
76 | {
|
77 | _angleCenter = angleCenter;
|
78 | [self setNeedsDisplay];
|
79 | }
|
80 |
|
81 | - (void)setAngle:(CGFloat)angle
|
82 | {
|
83 | _angle = angle;
|
84 | [self setNeedsDisplay];
|
85 | }
|
86 |
|
87 | + (CGPoint) getStartCornerToIntersectFromAngle:(CGFloat)angle AndSize:(CGSize)size
|
88 | {
|
89 | float halfHeight = size.height / 2.0;
|
90 | float halfWidth = size.width / 2.0;
|
91 | if (angle < 90)
|
92 | return CGPointMake(-halfWidth, -halfHeight);
|
93 | else if (angle < 180)
|
94 | return CGPointMake(halfWidth, -halfHeight);
|
95 | else if (angle < 270)
|
96 | return CGPointMake(halfWidth, halfHeight);
|
97 | else
|
98 | return CGPointMake(-halfWidth, halfHeight);
|
99 | }
|
100 |
|
101 | + (CGPoint) getHorizontalOrVerticalStartPointFromAngle:(CGFloat)angle AndSize:(CGSize)size
|
102 | {
|
103 | float halfWidth = size.width / 2;
|
104 | float halfHeight = size.height / 2;
|
105 | if (angle == 0) {
|
106 | // Horizontal, left-to-right
|
107 | return CGPointMake(-halfWidth, 0);
|
108 | } else if (angle == 90) {
|
109 | // Vertical, bottom-to-top
|
110 | return CGPointMake(0, -halfHeight);
|
111 | } else if (angle == 180) {
|
112 | // Horizontal, right-to-left
|
113 | return CGPointMake(halfWidth, 0);
|
114 | } else {
|
115 | // Vertical, top to bottom
|
116 | return CGPointMake(0, halfHeight);
|
117 | }
|
118 | }
|
119 |
|
120 | + (CGPoint) getGradientStartPointFromAngle:(CGFloat)angle AndSize:(CGSize)size
|
121 | {
|
122 | // Bound angle to [0, 360)
|
123 | angle = fmodf(angle, 360);
|
124 | if (angle < 0)
|
125 | angle += 360;
|
126 |
|
127 | // Explicitly check for horizontal or vertical gradients, as slopes of
|
128 | // the gradient line or a line perpendicular will be undefined in that case
|
129 | if (fmodf(angle, 90) == 0)
|
130 | return [BVLinearGradientLayer getHorizontalOrVerticalStartPointFromAngle:angle AndSize:size];
|
131 |
|
132 | // Get the equivalent slope of the gradient line as tan = opposite/adjacent = y/x
|
133 | float slope = tan(angle * M_PI / 180.0);
|
134 |
|
135 | // Find the start point by computing the intersection of the gradient line
|
136 | // and a line perpendicular to it that intersects the nearest corner
|
137 | float perpendicularSlope = -1 / slope;
|
138 |
|
139 | // Get the start corner to intersect relative to center, in cartesian space (+y = up)
|
140 | CGPoint startCorner = [BVLinearGradientLayer getStartCornerToIntersectFromAngle:angle AndSize:size];
|
141 |
|
142 | // Compute b (of y = mx + b) to get the equation for the perpendicular line
|
143 | float b = startCorner.y - perpendicularSlope * startCorner.x;
|
144 |
|
145 | // Solve the intersection of the gradient line and the perpendicular line:
|
146 | float startX = b / (slope - perpendicularSlope);
|
147 | float startY = slope * startX;
|
148 |
|
149 | return CGPointMake(startX, startY);
|
150 | }
|
151 |
|
152 | - (void)drawInContext:(CGContextRef)ctx
|
153 | {
|
154 | [super drawInContext:ctx];
|
155 |
|
156 | CGContextSaveGState(ctx);
|
157 |
|
158 | CGSize size = self.bounds.size;
|
159 | if (!self.colors || self.colors.count == 0 || size.width == 0.0 || size.height == 0.0)
|
160 | return;
|
161 |
|
162 |
|
163 | CGFloat *locations = nil;
|
164 |
|
165 | locations = malloc(sizeof(CGFloat) * self.colors.count);
|
166 |
|
167 | for (NSInteger i = 0; i < self.colors.count; i++)
|
168 | {
|
169 | if (self.locations.count > i)
|
170 | {
|
171 | locations[i] = self.locations[i].floatValue;
|
172 | }
|
173 | else
|
174 | {
|
175 | locations[i] = (1.0 / (self.colors.count - 1)) * i;
|
176 | }
|
177 | }
|
178 |
|
179 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
180 | NSMutableArray *colors = [[NSMutableArray alloc] initWithCapacity:self.colors.count];
|
181 | for (UIColor *color in self.colors) {
|
182 | [colors addObject:(id)color.CGColor];
|
183 | }
|
184 |
|
185 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations);
|
186 |
|
187 | free(locations);
|
188 |
|
189 | CGPoint start, end;
|
190 |
|
191 | if (_useAngle)
|
192 | {
|
193 | // Angle is in bearing degrees (North = 0, East = 90)
|
194 | // convert it to cartesian (N = 90, E = 0)
|
195 | float angle = (90 - _angle);
|
196 | CGPoint relativeStartPoint = [BVLinearGradientLayer getGradientStartPointFromAngle:angle AndSize:size];
|
197 |
|
198 | // Get true angleCenter
|
199 | CGPoint angleCenter = CGPointMake(
|
200 | _angleCenter.x * size.width,
|
201 | _angleCenter.y * size.height
|
202 | );
|
203 | // Translate to center on angle center
|
204 | // Flip Y coordinate to convert from cartesian
|
205 | start = CGPointMake(
|
206 | angleCenter.x + relativeStartPoint.x,
|
207 | angleCenter.y - relativeStartPoint.y
|
208 | );
|
209 | // Reflect across the center to get the end point
|
210 | end = CGPointMake(
|
211 | angleCenter.x - relativeStartPoint.x,
|
212 | angleCenter.y + relativeStartPoint.y
|
213 | );
|
214 | }
|
215 | else
|
216 | {
|
217 | start = CGPointMake(self.startPoint.x * size.width, self.startPoint.y * size.height);
|
218 | end = CGPointMake(self.endPoint.x * size.width, self.endPoint.y * size.height);
|
219 | }
|
220 |
|
221 | CGContextDrawLinearGradient(ctx, gradient,
|
222 | start,
|
223 | end,
|
224 | kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
225 | CGGradientRelease(gradient);
|
226 | CGColorSpaceRelease(colorSpace);
|
227 |
|
228 | CGContextRestoreGState(ctx);
|
229 | }
|
230 |
|
231 | @end
|