UNPKG

6.61 kBPlain TextView Raw
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