Code
使用效果
//包含头文件UIView+Gradient.h
[self.label az_setGradientBackgroundWithColors:@[[UIColor redColor],[UIColor orangeColor]] locations:nil startPoint:CGPointMake(0, 0) endPoint:CGPointMake(1, 0)];
[self.btn az_setGradientBackgroundWithColors:@[[UIColor redColor],[UIColor orangeColor]] locations:nil startPoint:CGPointMake(0, 0) endPoint:CGPointMake(1, 0)];
[self.tempView az_setGradientBackgroundWithColors:@[[UIColor redColor],[UIColor orangeColor]] locations:nil startPoint:CGPointMake(0, 0) endPoint:CGPointMake(1, 0)];
常规方案
说起颜色渐变一般来说有两种实现方式,一是使用CAGradientLayer
二是使用Core Graphics
的相关方法,具体可参考ios实现颜色渐变的几种方法。
问题
CAGradientLayer
可以说是实现起来比较方便的一个方案了,但是对于Autolayout
来说,我们需要更新CAGradientLayer
的frame
,这就得增加不少代码,而且代码会散布在其他方法中,这就不是很友好了。
优化方案
原理
我们知道UIView
显示的内容实际是绘制在CALayer
上的,默认情况下创建一个UIView
时会创建一个CALayer
作为UIView
的root layer
,那么如果我们直接把这个root layer
替换成CAGradientLayer
就能直接实现渐变效果,而且跟随UIView
的frame
变化。
那么如何替换呢?UIView
有个+layerClass
类方法,官方描述:
Returns the class used to create the layer for instances of this class.
This method returns the CALayer class object by default. Subclasses can override this method and return a different layer class as needed. For example, if your view uses tiling to display a large scrollable area, you might want to override this method and return the CATiledLayer class.
This method is called only once early in the creation of the view in order to create the corresponding layer object.
大概意思就是系统默认会返回CALayer
类,但是如果你重写了这个类方法,那么就会返你return
的类,从而创建你需要的layer
。
在实际实现这个分类的时候发现+layerClass
有点类似+load
方法,即只要文件在项目中,就会调用该方法,不需要显式包含头文件。
实现
我们创建一个UIView
的分类UIView+AZGradient
,利用runtime
添加CAGradientLayer
的一些属性以及设置背景色的方法,如下:
//UIView+AZGradient.h
@property(nullable, copy) NSArray *az_colors;
@property(nullable, copy) NSArray<NSNumber *> *az_locations;
@property CGPoint az_startPoint;
@property CGPoint az_endPoint;
+ (UIView *_Nullable)az_gradientViewWithColors:(NSArray<UIColor *> *_Nullable)colors locations:(NSArray<NSNumber *> *_Nullable)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
- (void)az_setGradientBackgroundWithColors:(NSArray<UIColor *> *_Nullable)colors locations:(NSArray<NSNumber *> *_Nullable)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
然后在.m文件中对重写类方法+layerClass
以及实现相关设置:
//UIView+AZGradient.m
+ (Class)layerClass {
return [CAGradientLayer class];
}
+ (UIView *)az_gradientViewWithColors:(NSArray<UIColor *> *)colors locations:(NSArray<NSNumber *> *)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
UIView *view = [[self alloc] init];
[view setGradientBackgroundWithColors:colors locations:locations startPoint:startPoint endPoint:endPoint];
return view;
}
- (void)az_setGradientBackgroundWithColors:(NSArray<UIColor *> *)colors locations:(NSArray<NSNumber *> *)locations startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {
NSMutableArray *colorsM = [NSMutableArray array];
for (UIColor *color in colors) {
[colorsM addObject:(__bridge id)color.CGColor];
}
self.colors = [colorsM copy];
self.locations = locations;
self.startPoint = startPoint;
self.endPoint = endPoint;
}
然后就能像最开始提到的那样轻松的一句话设置渐变背景色了。
注意事项
- 大部分View在设置
colors
属性后原有的backgroundColor
属性会失效,即不管backgroundColor
设置的是什么颜色,都会显示渐变色,要显示正常的backgroundColor
只需要将colors
设置成nil
。
// colors优先级高于backgroundColor的View
UIView
UIButton
UIImageView
UITextView
UITextField
UISlider
UIStepper
UISwitch
UISegmentedControl
在实测中发现
UILabel
在设置colors
后还是会显示backgroundColor
,要显示渐变色需要将backgroundColor
设置为clearColor
。虽然在
UIView
的分类中重写了+layerClass
,但是有可能存在一些View
已经重写了+layerClass
,那么就有可能该View的layer
并不是CAGradientLayer
,而是其他类型的layer
,如UILabel
的layer
其实是_UILabelLayer
,我们在设置layer
属性时不能确保这个layer
就是CAGradientLayer
,需要加个判断:
if ([self.layer isKindOfClass:[CAGradientLayer class]]) {
// do something
}
否则可能会出现崩溃。
- 对于
UILabel
这种貌似已经重写过+layerClass
方法的view,我目前是直接在其分类中再次重写+layerClass
方法
// UIView+Gradient.m
@implementation UILabel (Gradient)
+ (Class)layerClass {
return [CAGradientLayer class];
}
@end
将_UILabelLayer
替换为CAGradientLayer
除了colors
和backgroundColor
的优先级不同其他还有什么影响暂时不清楚。