Reputation: 1074
I'm trying to create a table view (custom class not UITableView) where its results have a gradient effect like in the example image below:
What I tried:
FAILED ATEMPT:
FAILED ATEMPT:
Last thing to try:
Suppose you have two colors, color1 and color2. A gradient can result by mixing these colors. In the picture above color1 = purple and color2 = orange. It would be easy to create a gradient effect by dividing the gradient in sections based on the number of results and then find the average color of each section and use it as the text color of each corresponding result.
For example:
5 results = 5 divisions.
The result is not as detailed because each text is a solid color but it is equally impressive when the text is small:
The problem is, for two colors like these:
Purple: 128.0, 0.0, 255.0
Orange: 255.0, 128.0, 0.0
How do you divide it in 5 sections and find the average of each section?
I could do this using the eyedropper tool in pixelmator but only if I knew the fixed number of results, won't work with 6 results.
I can't approach it with math, I don't know where to begin.
Any ideas?
Upvotes: 2
Views: 1181
Reputation: 767
For Xamarin iOS C#
public static List<UIColor> DividedColors(UIColor firstColor, UIColor secondColor, int sections)
{
nfloat firstRed = 0;
nfloat firstGreen = 0;
nfloat firstBlue = 0;
nfloat firstAlpha = 0;
firstColor.GetRGBA(out firstRed, out firstGreen, out firstBlue, out firstAlpha);
nfloat secondRed = 0;
nfloat secondGreen = 0;
nfloat secondBlue = 0;
nfloat secondAlpha = 0;
secondColor.GetRGBA(out secondRed, out secondGreen, out secondBlue, out secondAlpha);
nfloat Mix(nfloat first, nfloat second, nfloat ratio)
{
return first + ratio * (second - first);
}
List<UIColor> colors = new List<UIColor>();
var ratioPerSection = 1.0f / sections;
for (int i = 0; i < sections; i++)
{
var newRed = Mix(firstRed, secondRed, ratioPerSection * i);
var newGreen = Mix(firstGreen, secondGreen, ratioPerSection * i);
var newBlue = Mix(firstBlue, secondBlue, ratioPerSection * i);
var newAlpha = Mix(firstAlpha, secondAlpha, ratioPerSection * i);
var newColor = new UIColor(newRed, newGreen, newBlue, newAlpha);
colors.Add(newColor);
}
return colors;
}
Upvotes: 0
Reputation: 385670
So you want this:
There are several ways you could implement this. Here's a way that's pixel-perfect (it draws the gradient through the labels instead of making each label a solid color).
Make a subclass of UILabel
. In your subclass, override drawTextInRect:
to draw the gradient.
Let's declare the subclass like this:
IB_DESIGNABLE
@interface GradientLabel: UILabel
@property (nonatomic, weak) IBOutlet UIView *gradientCoverageView;
@property (nonatomic, strong) IBInspectable UIColor *startColor;
@property (nonatomic, strong) IBInspectable UIColor *endColor;
@end
Connect the gradientCoverageView
outlet to a view that covers the entire area of the gradient—so, a view that covers all five of the labels. This could be the superview of the labels, or it could just be a hidden view that you have set up to (invisibly) fill the same area.
Set startColor
to purple and endColor
to orange.
We'll draw the gradient-filled text (in our drawTextInRect:
override) in three steps:
Call super
to just draw the text normally.
Set the graphics context blend mode kCGBlendModeSourceIn
. This blend mode tells Core Graphics to draw only where the context has already been drawn, and to overwrite whatever was drawn there. Thus Core Graphics treats the text drawn by super
as a mask.
Draw the gradient with the start point at the top of the coverage view and the end point at the bottom of the coverage view, not at the top and bottom of the current label. Thus the gradient spans the entire coverage view, but is masked to only show up where the text of the current label was drawn in step 1.
Here's the implementation:
@implementation GradientLabel
- (void)drawTextInRect:(CGRect)rect {
[super drawTextInRect:rect];
CGContextRef gc = UIGraphicsGetCurrentContext();
NSArray *colors = @[(__bridge id)self.startColor.CGColor, (__bridge id)self.endColor.CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(CGBitmapContextGetColorSpace(gc), (__bridge CFArrayRef)colors, NULL);
UIView *coverageView = self.gradientCoverageView ?: self;
CGRect coverageRect = [coverageView convertRect:coverageView.bounds toView:self];
CGPoint startPoint = coverageRect.origin;
CGPoint endPoint = { coverageRect.origin.x, CGRectGetMaxY(coverageRect) };
CGContextSaveGState(gc); {
CGContextSetBlendMode(gc, kCGBlendModeSourceIn);
CGContextDrawLinearGradient(gc, gradient, startPoint, endPoint, 0);
} CGContextRestoreGState(gc);
CGGradientRelease(gradient);
}
@end
Here's the layout in my storyboard:
For all five of the GradientLabel
instances, I set the startColor
to purple and the endColor
to orange and I connected the gradientCoverageView
outlet to the enclosing stack view.
Upvotes: 1
Reputation: 9777
You can use math on the rgb values of the colors.
To get the rgb values, you can use the getRed:green:blue:alpha
method on UIColor
. Then all you have to do is average the colors together based on how many sections you need.
Here is a function that should return an array of colors based on a start and end color, and how many divisions you need.
Solution
func divideColors(firstColor: UIColor, secondColor: UIColor, sections: Int) -> [UIColor] {
// get rgb values from the colors
var firstRed: CGFloat = 0; var firstGreen: CGFloat = 0; var firstBlue: CGFloat = 0; var firstAlpha: CGFloat = 0;
firstColor.getRed(&firstRed, green: &firstGreen, blue: &firstBlue, alpha: &firstAlpha)
var secondRed: CGFloat = 0; var secondGreen: CGFloat = 0; var secondBlue: CGFloat = 0; var secondAlpha: CGFloat = 0;
secondColor.getRed(&secondRed, green: &secondGreen, blue: &secondBlue, alpha: &secondAlpha)
// function to mix the colors
func mix(_ first: CGFloat, _ second: CGFloat, ratio: CGFloat) -> CGFloat { return first + ratio * (second - first) }
// variable setup
var colors = [UIColor]()
let ratioPerSection = 1.0 / CGFloat(sections)
// mix the colors for each section
for section in 0 ..< sections {
let newRed = mix(firstRed, secondRed, ratio: ratioPerSection * CGFloat(section))
let newGreen = mix(firstGreen, secondGreen, ratio: ratioPerSection * CGFloat(section))
let newBlue = mix(firstBlue, secondBlue, ratio: ratioPerSection * CGFloat(section))
let newAlpha = mix(firstAlpha, secondAlpha, ratio: ratioPerSection * CGFloat(section))
let newColor = UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: newAlpha)
colors.append(newColor)
}
return colors
}
Your question is tagged as Objective-C
, but it should be easy enough to convert the Swift code above to Objective-C since you would use the same UIColor
API.
Here is some code to test the above function (perfect for a Swift playground).
Testing Code
let sections = 5
let view = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 50))
for (index, color) in divideColors(firstColor: UIColor.purple, secondColor: UIColor.orange, sections: sections).enumerated() {
let v = UIView(frame: CGRect(x: CGFloat(index) * 250 / CGFloat(sections), y: 0, width: 250 / CGFloat(sections), height: 50))
v.backgroundColor = color
view.addSubview(v)
}
view.backgroundColor = .white
Test Result
It also works for any number of sections and different colors!
Upvotes: 4
Reputation: 879
Well, here is how to do the solid-color mathy bit at the end:
If you have n + 1 colors, for 0 <= m <= n:
color.red[m] = (purple.red * (m/n)) + (orange.red * ((n-m)/n);
color.green[m] = (purple.green * (m/n)) + (orange.green * ((n-m)/n));
color.blue[m] = (purple.blue * (m/n)) + (orange.blue * ((n-m)/n));
Hope this helps.
Upvotes: 0