UIButton背景色

有一个需求,相信业务需求中总会碰到的吧。按钮点击前背景色是这样的,点击后背景色又是这样的。那么有什么好的方法实现呢?

UIButton 的多个方法可以设置不同 controlState 下的 UI 状态,比如 setTitlecolor,setTitle 等。而 backgroundColor 却没有对应的方法可以进行设置,大多时候是通过 setBackgroundImage: 来设置不同状态下的“背景颜色”。UIView -> UIControl -> UIButton,继承链是这样的。而 backgroundColor 是 UIView 的一个属性。着实无法通过 backgroundColor 来直接设置不同状态下的背景色。兴许 UIKit 的开发工程师也是考虑到了这点,才选择了通过 setBackgroudImage:forState 来设置 UIButton 的不同背景色吧。

其实可以通过 Category 来进行方法扩展。一种是直接生成 不同状态下 backgroundColor 的图片,然后用 setBackgroudImage:forState 来设置,一种是直接记录不同状态下 backgroundColor,直接进行 setBackgroundColor 。

方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
static NSString * const kColorForNormal = @"UIControlStateNormal";
static NSString * const kColorForHighlighted = @"UIControlStateHighlighted";
static NSString * const kColorForDisabled = @"UIControlStateDisabled";
static NSString * const kColorForSelected = @"UIControlStateSelected";
static NSString * const kColorForFocused = @"UIControlStateFocused";
static NSString * const kColorForApplication = @"UIControlStateApplication";
static NSString * const kColorForReserved = @"UIControlStateReserved";
@interface UIButton()
@property (nonatomic, strong) NSMutableDictionary *bgColorDict;
@end
@implementation UIButton (ACKit)
- (NSMutableDictionary *)bgColorDict {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setBgColorDict:(NSMutableDictionary *)bgColorDict {
objc_setAssociatedObject(self, @selector(bgColorDict), bgColorDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)ac_setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state {
if (!backgroundColor) return;
if (!self.bgColorDict) self.bgColorDict = [NSMutableDictionary dictionary];
NSString *key = [self fetchBgColorKey:state];
self.bgColorDict[key] = backgroundColor;
UIImage *bgImgColor = [self imageWithColor:backgroundColor];
[self setBackgroundImage:bgImgColor forState:state];
}
- (UIColor *)ac_backgroundColorForState:(UIControlState)state {
NSString *key = [self fetchBgColorKey:state];
return self.bgColorDict[key];
}
- (UIImage *)imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end

这是通过生成一张对应背景色图片从而间接设置背景图片来实现的。为了参考系统方法的,因此还是增多了一个 NSDictionary 来继续不同状态下的 backgroundColor,从而可以 ac_backgroundColorForState: 来得到对应的背景色。其实有点多余呐。(逃

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
static NSString * const kColorForNormal = @"UIControlStateNormal";
static NSString * const kColorForHighlighted = @"UIControlStateHighlighted";
static NSString * const kColorForDisabled = @"UIControlStateDisabled";
static NSString * const kColorForSelected = @"UIControlStateSelected";
static NSString * const kColorForFocused = @"UIControlStateFocused";
static NSString * const kColorForApplication = @"UIControlStateApplication";
static NSString * const kColorForReserved = @"UIControlStateReserved";
@interface UIButton()
@property (nonatomic, strong) NSMutableDictionary *bgColorDict;
@end
@implementation UIButton (ACKit)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
[self swizzleClass:class originalSelector:@selector(setSelected:) swizzledSelector:@selector(ac_setSelected:)];
[self swizzleClass:class originalSelector:@selector(setHighlighted:) swizzledSelector:@selector(ac_setHighlighted:)];
[self swizzleClass:class originalSelector:@selector(setEnabled:) swizzledSelector:@selector(ac_setEnabled:)];
});
}
+ (void)swizzleClass:(Class)class originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)ac_setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state {
if (!backgroundColor) return;
if (!self.bgColorDict) self.bgColorDict = [NSMutableDictionary dictionary];
NSString *key = [self fetchBgColorKey:state];
self.bgColorDict[key] = backgroundColor;
if (state == UIControlStateNormal) {
self.backgroundColor = backgroundColor;
}
}
- (UIColor *)ac_backgroundColorForState:(UIControlState)state {
NSString *key = [self fetchBgColorKey:state];
return self.bgColorDict[key];
}
- (void)ac_setSelected:(BOOL)selected {
[self ac_setSelected:selected];
NSString *key = [self fetchBgColorKey:(YES == selected ? UIControlStateSelected : UIControlStateNormal)];
UIColor *bgColor = self.bgColorDict[key];
if (bgColor) [self setBackgroundColor:bgColor];
}
- (void)ac_setHighlighted:(BOOL)highlighted {
[self ac_setHighlighted:highlighted];
if (highlighted) {
NSString *key = [self fetchBgColorKey: UIControlStateHighlighted];
UIColor *bgColor = self.bgColorDict[key];
if (bgColor) [self setBackgroundColor:bgColor];
} else {
if (!self.isSelected) {
NSString *key = [self fetchBgColorKey: UIControlStateNormal];
UIColor *bgColor = self.bgColorDict[key];
if (bgColor) [self setBackgroundColor:bgColor];
}
}
}
- (void)ac_setEnabled:(BOOL)enabled {
[self ac_setEnabled:enabled];
NSString *key = [self fetchBgColorKey:(NO == enabled ? UIControlStateDisabled : UIControlStateNormal)];
UIColor *bgColor = self.bgColorDict[key];
if (bgColor) [self setBackgroundColor:bgColor];
}
- (NSMutableDictionary *)bgColorDict {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setBgColorDict:(NSMutableDictionary *)bgColorDict {
objc_setAssociatedObject(self, @selector(bgColorDict), bgColorDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)fetchBgColorKey:(UIControlState)state {
NSString *key = kColorForNormal;
switch (state) {
case UIControlStateNormal:
key = kColorForNormal;
break;
case UIControlStateHighlighted:
key = kColorForHighlighted;
break;
case UIControlStateDisabled:
key = kColorForDisabled;
break;
case UIControlStateSelected:
key = kColorForSelected;
break;
case UIControlStateFocused:
key = kColorForFocused;
break;
case UIControlStateReserved:
key = kColorForReserved;
break;
case UIControlStateApplication:
key = kColorForApplication;
break;
default:
break;
}
return key;
}
@end

这是通过记录不同状态下的 backgroundColor ,再去用 swizzle 去替换掉原先 setSelected: / setEnabled: / setHighlighted: 方法,从而得到实现。但是因为直接判定得到三种状态( UIControlStateSelected / UIControlStateDisabled / UIControlStateHighlighted )。因此只能实现这三种状态下的切换。

又由于如果是在按钮点击事件中改变 selected 状态,因此在 ac_setHighlighted 方法进行加以判断,这是因为在测试过程中,发现 selected 后还会再次 highlighted。

结尾

觉得在开发过程中,一些好的方法还是设置抽离出来的。比如方法一中的通过颜色得到一个 UIImage。这样的方法其实也可以以扩展方法的形式存在 UIImage 中。再者 Swizzle 的方法其实也可以进行一个抽离。

最后,再上个源码吧。ACKit/ UIButton+ACKit