在iPhone上为UIView和UILabel添加渐变色

221

可能是重复问题:
如何在iPhone应用中手动绘制渐变?

我的应用程序需要在UIViewUILabel中显示文本,但背景必须是渐变色而不是单一的UIColor。使用图形程序创建所需外观是不可取的,因为文本可能根据从服务器返回的数据而异。

有没有人知道快速解决这个问题的方法? 非常感谢您的想法。

7个回答

780

我知道这是一个较旧的讨论串,但为了以后的参考:

从 iPhone SDK 3.0 开始,可以通过使用新的 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];

看一下CAGradientLayer文档。你可以选择性地指定起点和终点(如果你不想要从顶部到底部直接的线性渐变),甚至还可以指定映射到每个颜色的特定位置。


116
如果有人考虑实施上述其他解决方案时,请注意我的评论。采用这个方法非常简单(但请记住,您需要添加QuartzCore框架,并在您的类中导入#import <QuartzCore/QuartzCore.h>)。 - Justin Searls
34
有人可能会应用额外的效果,例如cornerRadius。为确保一切顺利进行,他们必须在这里添加更多代码:view.layer.masksToBounds = YES; - iwat
6
我的实验表明,使用固定梯度图像的图像比使用CAGradientLayer显示更好的性能。 - Mike
11
嗯,使用这个解决方案后我的标签文字消失了。 - HiveHicks
11
我发现,如果我想用 CAGradientLayer 来给一个 UILabel 添加渐变色,那么我就需要将 backgroundColor 设置为 [UIColor clearColor](或等价的值)。 - Thomas Müller
显示剩余21条评论

122

你可以像Mike的回答中所指出的那样使用Core Graphics来绘制渐变。作为更详细的示例,你可以创建一个UIView子类用作UILabel的背景。在该UIView子类中,覆盖drawRect:方法并插入类似以下代码:

- (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); 
}

这个例子创建了一个从UIView顶部到其垂直中心绘制的白色高光样式渐变。您可以将UIViewbackgroundColor设置为任何你喜欢的颜色,并且该高光效果将绘制在该颜色上方。您还可以使用CGContextDrawRadialGradient函数绘制径向渐变。

您只需适当地调整此UIView的大小并将UILabel作为其子视图添加即可获得所需的效果。

编辑(4/23/2009):根据St3fan的建议,我已经在代码中用bounds替换了视图的frame。这纠正了当视图的原点不是(0,0)时的情况。


2
我尝试了这段代码,但是它不正确。应该将 currentFrame 设置为 self.bounds 而不是 self.frame。 - Stefan Arentz
1
你说得对,谢谢指出。我已经纠正了自己的代码,但忘记更新这个。当视图的起点位置与0不同时,会遇到问题。渐变然后会偏离垂直中心绘制。 - Brad Larson
你能告诉我在使用如下代码时,你是如何确定颜色的吗?CGFloat components[8] = { 1.0, 1.0, 1.0, 0.35, 1.0, 1.0, 1.0, 0.06 }; 比如说,你是如何知道 1.0 和 0.35 分别代表什么? - fulvio
@Fulvio - 这是两个颜色,由红、绿、蓝和 alpha 组件的两组表示。这些组件被归一化为 1.0,因此不透明的黑色是 (0.0, 0.0, 0.0, 1.0),不透明的白色是 (1.0, 1.0, 1.0, 1.0)。 - Brad Larson
不要忘记添加设备比例: CGRect currentBounds = (CGRect) { 0, 0, self.bounds.size.width * [UIScreen mainScreen].scale, self.bounds.size.height * [UIScreen mainScreen].scale, }; - myeyesareblind
显示剩余4条评论

73

注意:以下结果适用于旧版本的iOS,在iOS 13上测试时,步进不会发生。我不知道步进是在哪个版本的iOS中被移除的。


使用CAGradientLayer时,与CGGradient相比,渐变不够平滑,而是有明显的步进。请参见此示例:this example 为获得更具吸引力的结果,最好使用CGGradient。

7
请问您能否发一下与Mirko Froehlich使用CGGradient的示例代码相等的代码? - zekel
@MattDiPasquale 我手头没有用来生成这个的代码,但我记得终点使用了相同的颜色。无论如何,我认为重要的是渐变的质量。无论如何,你应该能够自己复制结果。(当时我使用的是iOS 4.x,所以在iOS 5下结果可能会有所不同。) - ThomasW
21
重要提示:我最近删除了我的CGGradient代码,并用CAGradientLayer替换了它。你知道为什么吗?因为我需要在30个单元格上绘制,而CGGradient会严重影响性能。相比之下,CAGradient的性能要好得多(这解释了微小的质量差异)。 - Mazyod
1
如果您想要添加渐变的视图高度较小,那么我认为CAGradientLayer是您可以承受的妥协。如果视图高度很大,则最好使用CGGradient。因此,在滚动视图中放置更高的视图的可能性非常小。 - Deepak G M
7
对于 Mazyod 的评论,如果你发现自己以同样的方式多次生成相同的渐变(或任何其他绘图操作),并且这影响了性能,请考虑只执行一次,将结果缓存为 UIImage 并重复使用。对于多层绘制也非常有效。 - SaltyNuts
显示剩余3条评论

45

您还可以使用一个像素宽的图形图像作为渐变,并将视图属性设置为扩展该图形以填充视图(假设您考虑的是简单的线性渐变而不是某种径向图形)。


13
采用这种方法时,请确保拥有两种不同的渐变:一种是用于普通显示器的,另一种是用于Retina显示器的。 - Kamchatka
10
与编码解决方案相比,此解决方案的一个重要好处是颜色渐变通常是一种装饰属性,实际上根本不应该在代码中表达。这就是图形设计人员的工作。编写代码来表达颜色是逻辑与表示的差异较大。当营销团队决定更改颜色时,图形设计人员询问如何操作时,你告诉他只需调整 MyLabelView.m 中 CAGradientLayer 上的颜色数组,他可能会不喜欢这个答案......也确实不应该。对于单人商店来说可能无关紧要,但对于团队而言很重要。 - Nate
2
我同意Nate的观点,让设计师尽可能轻松地更改资产是有好处的。 - Kendall Helmstetter Gelner
2
@Nate - 你的逻辑代码应该与你的展示代码分开。这并不意味着你唯一的解决方案是拥有一个图形文件,而且坦率地说,对于应用程序中的每个位图,你将为自己在未来增加更多的工作量。渐变代码总是看起来很棒。你的位图只会在苹果再次增加屏幕分辨率之前好看。 - ArtOfWarfare
4
同样适用于高度动态的视图,比如一个单元格的高度根据它要容纳的文本量而定。 - Adam
显示剩余3条评论

24

Mirko Froehlich的回答对我有效,除了当我想要使用自定义颜色时。 诀窍是使用色调、饱和度和亮度来指定UI颜色,而不是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];

要获取颜色的色调、饱和度和亮度,请使用内置的Xcode颜色选择器并转到HSB选项卡。在此视图中,色调以度为单位测量,因此将该值除以360以获得您想要在代码中输入的值。


1
我可以添加这个CAGradientLayer,但是如何从myView中删除它?我想在绘制CAGradientLayer之前将视图恢复为以前的状态。有没有方法可以做到这一点? - Ajay Sharma
我会循环遍历myView.layer.sublayers,直到找到CAGradientLayer,然后调用removeFromSuperlayer。for (CALayer *currentLayer in [self.view.layer sublayers]) { if([currentLayer isKindOfClass: [CAGradientLayer class]] ) { [currentLayer removeFromSuperlayer]; } } - Robert Wagstaff
为什么不把梯度层存储为实例属性,并在不再需要时将其删除呢? - return true

15
这是我得到的可行解决方案- 在xCode的IB中将UIButton设置为透明/无背景图片。
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];

好的,更改一下-我现在子类化UIButton并使用highlight方法:“-(void)setHighlighted:(BOOL)highlight {如果高亮不等于self.highlighted){”在其中,执行图层切换。效果很棒。 - Anna Billstrom
并在此详细介绍了它:http://www.banane.com/2012/06/01/iphone-gradient-buttons-with-highlighting/ - Anna Billstrom
如何从View中删除CAGradientLayer? - Ajay Sharma
如果我没记错的话,从按钮视图层中找到它并将其移除。 - Anna Billstrom

3

我使用一个UIImageView作为子视图来实现这个功能。这个UIImageView指向的图片是一个渐变色。然后我在UIView中设置了一个背景颜色,这样就得到了一个带有颜色渐变的视图。接下来,我可以按照需要使用这个视图,而且所有绘制的内容都会在这个渐变色视图下面。通过在UIImageView上添加第二个视图,您可以选择您的绘制是在渐变色视图之下还是之上...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接