Gradients on UIView and UILabels On iPhone [duplicate]
Asked Answered
K

7

221

Possible Duplicate:
Manually drawing a gradient in iPhone apps?

My application needs to display text in either a UIView or UILabel but the back ground must be a gradient as opposed to a true UIColor. Using a graphics program to create desired look is no good as the text may vary depending on data returned from a server.

Does anyone know the quickest way to tackle this? Your thoughts are greatly appreciated.

Kp answered 7/1, 2009 at 20:58 Comment(0)
F
45

You could also use a graphic image one pixel wide as the gradient, and set the view property to expand the graphic to fill the view (assuming you are thinking of a simple linear gradient and not some kind of radial graphic).

Fredericksburg answered 8/1, 2009 at 0:59 Comment(8)
Hey Kendall, thanks man you triggered a duh switch for me. Solved it simply by placing a gradient into the UIImageView and place the dynamic label on top of that. Thanks to all for your help. TonyKp
With this approach, make sure to have two different gradients: one for regular displays and one for retina displays.Regionalism
One important benefit of this solution, over the coded solutions, is that color gradients are normally a decoration property, and really shouldn't be expressed in code at all. This is what graphics guys are for. Writing code to express colors is a poor separation of logic from presentation. When the marketing team decides to change the colors, and a graphics guy asks how to do that, he's not going to like being told that you just adjust the color array on the CAGradientLayer in MyLabelView.m ... and nor should he. Doesn't matter much for a one person shop, but for teams it does.Juliannajulianne
I agree with Nate on that, it can be good to let designers change assets as easily as possible.Fredericksburg
@Juliannajulianne - Your code for your logic should be separate from your code for your presentation. That doesn't mean your only possible solution is to have a graphics file, and quite frankly, for each bitmap in your application, you're setting yourself up for more work in the future. Gradient code will always look great. Your bitmap will only look good until Apple decides to increase the resolution of the screen again.Histolysis
Think of using a UIImageView for gradients as the ultimate way to separate logic from presentation. It is more work but the main thing to consider is it's work for the people who directly need the most control over it. It's not more coding work and that's important too.Fredericksburg
Ditto for views with dynamic heights, like a cell whose height depends on how much text it has to hold.Stela
Why would you be changing 50 xib files if you just have one gradient image that you change out? In theory it would be just as easy as changing code. The place I see using code for gradients is when you need to dynamically create the gradient for some reason.Fredericksburg
H
780

I realize this is an older thread, but for future reference:

As of iPhone SDK 3.0, custom gradients can be implemented very easily, without subclassing or images, by using the new CAGradientLayer:

 UIView *view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 100)] autorelease];
 CAGradientLayer *gradient = [CAGradientLayer layer];
 gradient.frame = view.bounds;
 gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
 [view.layer insertSublayer:gradient atIndex:0];

Take a look at the CAGradientLayer docs. You can optionally specify start and end points (in case you don't want a linear gradient that goes straight from the top to the bottom), or even specific locations that map to each of the colors.

Haha answered 19/12, 2009 at 0:38 Comment(25)
Just in case my comment catches anyone's eye as they consider implementing the other solutions above, DO THIS ONE. It couldn't be easier (but do remember that you'll need to add the QuartzCore framework and import #import <QuartzCore/QuartzCore.h> in your class).Mansell
I like this but I'm wondering how the performance of this compares to pre-rendering an image with the gradient. I'm particularly thinking about the case of table view cells, where during scrolling there could be lots of redrawing.Stumper
Good point. I only used this as a fixed background for the entire view, and didn't notice any significant overhead. Not sure how well it performs on individual table cells. Should be easy to find out, though.Haha
Someone may apply additional effect e.g., cornerRadius. They must add more code here: view.layer.masksToBounds = YES; to make sure thing goes smooth.Lanark
What way provides the best performance? Using CAGradientLayer or UIImageViews?Kristine
My experimentation has shown that images with a fixed gradient image show better performance that using CAGradientLayer.Featherstitch
Hm. My label text disappears when I use this solution.Itinerancy
Hey, this is terrific - thanks! ("He wrote over a year later.") I wonder if I can use this for table view cells, in selected and unselected backgrounds? I s'pose I just insert sublayers to each of those views on the cell. Maybe. (I presume the selected state already has a gradient in play though - hmm ... !)Ilonailonka
@Mike: How big are the performance differences? Do you have some specific tests / metrics to back your claim?Magnifico
@Rafael A pretty big difference. I wrote an app very similar to the mail app and use a gradient filled blue ball in each cell in a table to mark when a message was unread. Scrolling the table was significantly slower than the alternative of just having a pre-made image for the ball. Try it and see.Featherstitch
@Itinerancy I found that if I use a CAGradientLayer for a UILabel that I have to set the backgroundColor to [UIColor clearColor] (or equivalent)Dyscrasia
I'm trying to use this for a uitableviewcell and the gradient overflows out of the right side of the cell. What am I doing wrong?Discontinuous
Tip: to use the highlight (and show another gradient with highlight), subclass UIButton and use the "- (void)setHighlighted:(BOOL)highlight { if (highlight != self.highlighted){... }} " method. Then I pop a layer switching in there (from put normal layer at zero and highlight at 1). Works great.Compose
I got it working, with gradients, highlighting (gradient on highlight too) rounded corners, etc. You can also download the code:banane.com/2012/06/01/iphone-gradient-buttons-with-highlightingCompose
I added this to my table cell. I have have 9 rows visible at load. When I load the table via a button push on my main view, it takes a good 10 seconds for the table to push. Zero seconds without the gradient. Why such a delay?Microdont
What is drawn using drawRect: still is under this gradient. Just a fun fact.Psychokinesis
@Featherstitch - I wonder if you're using deque properly? Should not the drawing code only have to execute once per visible cell, and then never again if you're scrolling because it'll just reuse old cells?Histolysis
You might need to link the QuartzCore lib: #7464899 Check the solution and the comment below =)Stinger
fine code to apply gradient.really usefullAmelina
Not working for UILabel. It is not showing gradient color in background.Hake
Any idea how to make a radial gradient?Distributor
This doesn't seem to work as a gradient for an all-app background. The layer doesn't auto-resize itself with the view.Allred
@Allred see my answer here: https://mcmap.net/q/120500/-manually-drawing-a-gradient-in-iphone-appsIllustrate
@MirkoFroehlich please check this question: #39256385Philter
one upvote for simple Answer.. Very Simple and useful, Thanks..! But how can i Acheive it for UILabel Text, not for background..... any idea...?Nonmetallic
J
122

You can use Core Graphics to draw the gradient, as pointed to in Mike's response. As a more detailed example, you could create a UIView subclass to use as a background for your UILabel. In that UIView subclass, override the drawRect: method and insert code similar to the following:

- (void)drawRect:(CGRect)rect 
{
    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    CGGradientRef glossGradient;
    CGColorSpaceRef rgbColorspace;
    size_t num_locations = 2;
    CGFloat locations[2] = { 0.0, 1.0 };
    CGFloat components[8] = { 1.0, 1.0, 1.0, 0.35,  // Start color
         1.0, 1.0, 1.0, 0.06 }; // End color

    rgbColorspace = CGColorSpaceCreateDeviceRGB();
    glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);

    CGRect currentBounds = self.bounds;
    CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
    CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
    CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);

    CGGradientRelease(glossGradient);
    CGColorSpaceRelease(rgbColorspace); 
}

This particular example creates a white, glossy-style gradient that is drawn from the top of the UIView to its vertical center. You can set the UIView's backgroundColor to whatever you like and this gloss will be drawn on top of that color. You can also draw a radial gradient using the CGContextDrawRadialGradient function.

You just need to size this UIView appropriately and add your UILabel as a subview of it to get the effect you desire.

EDIT (4/23/2009): Per St3fan's suggestion, I have replaced the view's frame with its bounds in the code. This corrects for the case when the view's origin is not (0,0).

Jessalyn answered 7/1, 2009 at 21:39 Comment(9)
I tried this code but it is not correct. Instead of setting currentFrame to self.frame it should be set to self.bounds.Hayse
You're right, thanks for pointing it out. I had since corrected my own code, but forgot that this needed updating. The case that gets you is when the origin of the view has a different Y position than 0. The gradient then gets drawn offset from the vertical center.Jessalyn
Can be this applied to UIImage or UIImageView to have any image with gradient?Hambrick
You can place this over a UIImageView to add a gradient. For altering the contents of an image, you might need to draw the image first to a context, draw the gradient above it in that context, and then save the context as a new image.Jessalyn
Can you tell me how you determine the colors when using the following: CGFloat components[8] = { 1.0, 1.0, 1.0, 0.35, 1.0, 1.0, 1.0, 0.06 }; How do you know what 1.0 and 0.35 are for example?Sateen
@Fulvio - Those are two colors, represented by two sets of the red, green, blue, and alpha components. These components are normalized to 1.0, so that opaque black would be (0.0, 0.0, 0.0, 1.0) and opaque white would be (1.0, 1.0, 1.0, 1.0).Jessalyn
How do you call this method and/or apply it to a UIImageView?Microdont
Not possible for UIImageView - it doesn't call drawRect.Psychokinesis
Don't forget to add the device scale: CGRect currentBounds = (CGRect) { 0, 0, self.bounds.size.width * [UIScreen mainScreen].scale, self.bounds.size.height * [UIScreen mainScreen].scale, };Basir
T
73

Note: The results below apply to older versions of iOS, but when testing on iOS 13 the stepping doesn't occur. I don't know for which version of iOS the stepping was removed.


When using CAGradientLayer, as opposed to CGGradient, the gradient is not smooth, but has noticeable stepping to it. See this example:

To get more attractive results it is better to use CGGradient.

Topdrawer answered 13/1, 2011 at 3:18 Comment(8)
Would you mind posting code equivalent to Mirko Froehlich's example with CGGradient?Bizarre
Brad Larson's code above using CGGradient is good enough. I would have made my comment a comment on one of those answers, but I don't have enough points yet.Topdrawer
How can you tell? Your gradients end (at the bottom) with different colors.Monitorial
@MattDiPasquale I don't have the code I used to generate this on hand, but as I recall the end points used the same colors. In any case I doubt it matters since my point is about the quality of the gradient. In any case you should be able to reproduce the results yourself. (I was using iOS 4.x at the time, so results could be different with iOS 5.)Topdrawer
IMPORTANT: I deleted my CGGradient code recently and replaced it with CAGradientLayer. You know why? I was drawing it on like 30 Cells, and the performance impact was BIG. CAGradient was much better performance-wise (Explains the tiny quality difference).Pharyngoscope
If the height of the view you want to add gradient is small, CAGradientLayer is compromise you can afford, I guess. If the view height is significant, better use a CGGradient. As such, its highly unlikely that you will put a view of larger height in a scroll view.Halima
To Mazyod's comment, if you find yourself generating the same gradient (or any other drawing operation) many times in the same way and that affects performance, consider doing it just once, caching the result into an UIImage and using that repeatedly. Works great for multi-layer drawings too.Brinna
Updated my answer. In my initial tests this problem doesn't occur on iOS 13.Topdrawer
F
45

You could also use a graphic image one pixel wide as the gradient, and set the view property to expand the graphic to fill the view (assuming you are thinking of a simple linear gradient and not some kind of radial graphic).

Fredericksburg answered 8/1, 2009 at 0:59 Comment(8)
Hey Kendall, thanks man you triggered a duh switch for me. Solved it simply by placing a gradient into the UIImageView and place the dynamic label on top of that. Thanks to all for your help. TonyKp
With this approach, make sure to have two different gradients: one for regular displays and one for retina displays.Regionalism
One important benefit of this solution, over the coded solutions, is that color gradients are normally a decoration property, and really shouldn't be expressed in code at all. This is what graphics guys are for. Writing code to express colors is a poor separation of logic from presentation. When the marketing team decides to change the colors, and a graphics guy asks how to do that, he's not going to like being told that you just adjust the color array on the CAGradientLayer in MyLabelView.m ... and nor should he. Doesn't matter much for a one person shop, but for teams it does.Juliannajulianne
I agree with Nate on that, it can be good to let designers change assets as easily as possible.Fredericksburg
@Juliannajulianne - Your code for your logic should be separate from your code for your presentation. That doesn't mean your only possible solution is to have a graphics file, and quite frankly, for each bitmap in your application, you're setting yourself up for more work in the future. Gradient code will always look great. Your bitmap will only look good until Apple decides to increase the resolution of the screen again.Histolysis
Think of using a UIImageView for gradients as the ultimate way to separate logic from presentation. It is more work but the main thing to consider is it's work for the people who directly need the most control over it. It's not more coding work and that's important too.Fredericksburg
Ditto for views with dynamic heights, like a cell whose height depends on how much text it has to hold.Stela
Why would you be changing 50 xib files if you just have one gradient image that you change out? In theory it would be just as easy as changing code. The place I see using code for gradients is when you need to dynamically create the gradient for some reason.Fredericksburg
U
24

Mirko Froehlich's answer worked for me, except when i wanted to use custom colors. The trick is to specify UI color with Hue, saturation and brightness instead of RGB.

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = myView.bounds;
UIColor *startColour = [UIColor colorWithHue:.580555 saturation:0.31 brightness:0.90 alpha:1.0];
UIColor *endColour = [UIColor colorWithHue:.58333 saturation:0.50 brightness:0.62 alpha:1.0];
gradient.colors = [NSArray arrayWithObjects:(id)[startColour CGColor], (id)[endColour CGColor], nil];
[myView.layer insertSublayer:gradient atIndex:0];

To get the Hue, Saturation and Brightness of a color, use the in built xcode color picker and go to the HSB tab. Hue is measured in degrees in this view, so divide the value by 360 to get the value you will want to enter in code.

Unbutton answered 17/6, 2012 at 0:41 Comment(3)
I can add this CAGradientLayer, but how can one remove it from the myView ? I would like to have the view as earlier before drawing the CAGradientLayer.Is there any for this ?Margalit
I would loop through myView.layer.sublayers until you get the CAGradientLayer, then call removeFromSuperlayer. for (CALayer *currentLayer in [self.view.layer sublayers]) { if([currentLayer isKindOfClass: [CAGradientLayer class]] ) { [currentLayer removeFromSuperlayer]; } }Unbutton
Wtf, why not just store the gradient layer as an instance property and remove it when not needed anymore?Aluino
C
15

This is what I got working- set UIButton in xCode's IB to transparent/clear, and no bg image.

UIColor *pinkDarkOp = [UIColor colorWithRed:0.9f green:0.53f blue:0.69f alpha:1.0];
UIColor *pinkLightOp = [UIColor colorWithRed:0.79f green:0.45f blue:0.57f alpha:1.0];

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = [[shareWordButton layer] bounds];
gradient.cornerRadius = 7;
gradient.colors = [NSArray arrayWithObjects:
                   (id)pinkDarkOp.CGColor,
                   (id)pinkLightOp.CGColor,
                   nil];
gradient.locations = [NSArray arrayWithObjects:
                      [NSNumber numberWithFloat:0.0f],
                      [NSNumber numberWithFloat:0.7],
                      nil];

[[recordButton layer] insertSublayer:gradient atIndex:0];
Compose answered 25/5, 2012 at 3:54 Comment(4)
Ok change- I now subclass the UIButton and use the highlight method: "- (void)setHighlighted:(BOOL)highlight { if (highlight != self.highlighted){" inside this, do the layer switcheroo. works great.Compose
And, wrote about it in detail here: banane.com/2012/06/01/iphone-gradient-buttons-with-highlightingCompose
How one can remove this CAGradientLayer from the View ?Margalit
Find it and remove it from the button's layer of views, if I recall.Compose
P
3

I achieve this in a view with a subview that is an UIImageView. The image the ImageView is pointing to is a gradient. Then I set a background color in the UIView, and I have a colored gradient view. Next I use the view as I need to and everything I draw will be under this gradient view. By adding a second view on top of the ImageView, you can have some options whether your drawing will be below or above the gradient...

Phonology answered 12/5, 2009 at 6:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.