前言 iOS 开发中,UITableView 随处可见,而在点击 UITableView 的 cell 的时候,若是他的子视图设置了透明颜色之外的颜色,子视图的背景颜色会进行相关的改变,效果以下图。php  这种状况是否是有种似曾相识的感受 若是没有,我再举几个不少人使用的 App 上对于这种状况处理不佳的例子,注意左右对比git   产生这种状况的缘由是由于 cell 在点击的时候会将子视图的背景颜色设置为透明色,而这里微博的分割线和简书的蓝色小圈圈应该是用了一种 UIView 设置了背景颜色来实现的,因而在点击过程当中,原本是灰色的分割线,和原本是蓝色的小圈圈,都“消失”了。github 网上对于这种状况的解决方案,大可能是下面这种解决方式:函数
修改 cell 的选中样式:布局
-
cell.selectionStyle = UITableViewCellSelectionStyleNone;
这种解决方案,能够正确的达到点击的时候子视图背景颜色再也不改变,只是美中不足的是,这种方法不只去除了咱们不想要的子视图背景颜色改变的效果,还去除了 cell 自己 contentView 的背景颜色改变的效果。测试 此时点击 cell ,用户再也不会感受到有任何变化,为了取消子视图背景颜色改变效果,而取消 cell 的选中效果,这种作法不太友好。atom 此时又有网友提出,本身实现这种选中效果,在 cell 的 contentView 的最下层添加一个 button,很好的思路,只是,若是此时你的 cell 已经使用 xib 布局的差很少了,或者使用纯代码写的差很少了,再向 cell 的 contentView 和你所添加的 subview 中间添加一个 button 就显得有点麻烦。spa 懒癌晚期,想有一劳永逸的解决办法,最好是那种不对原工程作任何改动的 下面是我思考的过程:.net 我先看看 cell 的 contentView 中全部子视图调用设置背景颜色的方法时的函数调用栈,看看颜色改变成透明眼色以前调用了哪些方法code cell 子视图更改颜色以前,究竟作了些什么? 我新建了一个 .m 文件,并在其中使用 runtime 的 Method Swizzle 对设置背景颜色方法进行替换,在新的设置背景颜色方法中打印函数调用栈,具体代码以下:  这里若是不懂运行时的相关用法,能够自行去了解一下 runtime 的相关使用以及概念。 而后咱们运行进行相关测试,发现打印了两次函数调用栈,意味着,在点击开始和结束以后,cell 的子视图有两次被设置成了透明颜色。其中一次是点击开始时,改变子视图背景颜色为透明色,还有一次是点击结束时,改变子视图背景颜色为透明色。   对比点击先后的函数调用栈,咱们能够观察到,在点击 cell 的设置子视图背景颜色的先后都调用了
-
_setOpaque:forSubview:
showSelectedBackgroundView:animated:
setHighlighted:animated:
等方法; 其中:
- 前两个方法是私有 API,使用私有 API 的结果没法预料。。可能会被苹果爸爸拒绝上线,因此,这里不考虑他们
- 最后一个
setHighlighted 方法在点击时,传入的 highlighted 为 YES,点击结束传入的为 NO。彷佛能够考虑在这里里一个 FLAG
- 另外,在 cell 默认加载出来的时候也会调用
setHighlighted 方法,只不过传入的 highlighted 为 NO
- 那么此处我已经肯定能够用最后一个方法立一个 FLAG 了,也就是我在这里,只须要在
highlighted 传入为 YES 的时候让子视图禁止调用改变背景颜色的方法,而后在再次为 NO 的时候,恢复能够调用改变背景颜色。
- 这彷佛是一个可行的方法,可是,不要忘了,函数调用栈中的方法,是越早调用的方法越在下面,这里
setHighlighted 方法的调用在最后一次设置背景颜色为透明以前,也就是说,若是我按照上面的方法作了,那我就只能阻止一次设置背景颜色为透明
- 我想到的解决方案,再立一个 FLAG,而后,在
highlighted 为 YES 的时候,让子视图禁止设置背景颜色,而后第一次设置为透明会被阻止,第二次的时候,判断 FLAG,经过 FLAG 再阻止下一次设置背景颜色为透明
禁止设置子视图背景颜色的具体代码以下:  这段代码使用了 runtime 在交换的设置背景颜色方法中,对经过 runtime 添加的 forbidSetBackgroundColor 属性进行判断,若是设置为 YES,则不能设置背景颜色。 接下来对 cell 的 setHighlighted 进行替换,在合适的时机,设置 UILabel 可否改变背景颜色便可 具体代码以下:  接下来,咱们该立 FLAG 了  咱们添加了一个新的属性,当 highlighted 为 YES 的时候,forbidSetBackgroundColor 会变成 YES,此时,FLAG:shouldIgnoreSetClearBackgroundColorHandle 设置为 YES,下一次 forbidSetBackgroundColor 不起做用的时候,经过 shouldIgnoreSetClearBackgroundColorHandle 能够再次阻止背景色被设置成透明色的操做。 写完这些代码,我想我之后又能够偷懒了,不须要之后每次去关心 cell 点击改变子视图背景颜色的问题,只须要新增一个 .m 文件,不须要改动原先一句代码,就能够实现点击 cell 先后,子视图颜色不会改变,并且也保留了原生的点击效果 不足之处 这篇文章中所实现的仅仅是 UITableViewCell 在点击过程当中 UILabel 的颜色不会改变,其余的子控件,诸如,UIButton,UIView 之类的视图,背景色仍然会改变,并且也不能解决多个视图层叠的背景色被清空的状况,因而,我写了一个分类:CoderGin/CGCellContentViewManager
- 实现了全部带有非透明颜色的 UI 控件在 UITableViewCell 中,点击过程当中不会改变背景颜色的效果
- 使用方法很简单,直接将分类拖入工程中,便可使用
- 欢迎你们使用和 Star,欢迎提出修改意见,更欢迎提出 bug
感谢 在这里要感谢两个网友 angelen10 和 MuYanQin 对我所写的代码的 bug 提出,欢迎更多人指出个人 bug。 结尾
- 最后附上 .m 文件全部的代码
- 使用方法很简单,在工程中新增一个 .m 文件,把下方的全部代码粘贴上去
- 不须要你调用和改变任何原先的代码,这段代码就会自动工做了,
- 若是你有什么疑问,或者发现了我代码中有什么不对的地方,欢迎评论和纠错
- 若是你想研究和阅读本篇文章测试所写的工程,能够到个人 GitHub: CoderGin/UITableViewDemo上下载
-
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
-
@interface UILabel (RuntimeTest)
-
@property (nonatomic, assign) BOOL forbidSetBackgroundColor;
-
@property (nonatomic, assign) BOOL shouldIgnoreSetClearBackgroundColorHandle;
-
@end
-
@implementation UILabel (RuntimeTest)
-
+ (void)load {
-
SEL originalSelector = @selector(setBackgroundColor:);
SEL swizzledSelector = @selector(ex_setBackgroundColor:);
-
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
-
BOOL success = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
-
- (void)ex_setBackgroundColor:(UIColor *)backgroundColor {
-
if (self.forbidSetBackgroundColor){
self.shouldIgnoreSetClearBackgroundColorHandle = YES;
} else {
if (backgroundColor == [UIColor clearColor]) {
if (self.shouldIgnoreSetClearBackgroundColorHandle) {
self.shouldIgnoreSetClearBackgroundColorHandle = NO;
} else {
[self ex_setBackgroundColor:backgroundColor];
}
} else {
[self ex_setBackgroundColor:backgroundColor];
}
}
}
-
- (BOOL)shouldIgnoreSetClearBackgroundColorHandle {
-
id value = objc_getAssociatedObject(self, _cmd);
if (value == nil) {
objc_setAssociatedObject(self, _cmd, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
-
- (void)setShouldIgnoreSetClearBackgroundColorHandle:(BOOL)shouldIgnoreSetClearBackgroundColorHandle {
-
objc_setAssociatedObject(self, @selector(shouldIgnoreSetClearBackgroundColorHandle), @(shouldIgnoreSetClearBackgroundColorHandle), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-
- (BOOL)forbidSetBackgroundColor {
-
id value = objc_getAssociatedObject(self, _cmd);
if (value == nil) {
objc_setAssociatedObject(self, _cmd, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
-
- (void)setForbidSetBackgroundColor:(BOOL)forbidSetBackgroundColor {
-
objc_setAssociatedObject(self, @selector(forbidSetBackgroundColor), @(forbidSetBackgroundColor), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-
@end
-
@implementation UITableViewCell (RuntimeTest)
-
+ (void)load {
-
SEL originalSelector = @selector(setHighlighted:animated:);
SEL swizzledSelector = @selector(ex_setHighlighted:animated:);
-
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
-
BOOL success = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
-
- (void)ex_setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
-
if (highlighted == YES) {
for (UIView *subview in self.contentView.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)subview;
label.forbidSetBackgroundColor = YES;
}
}
} else {
for (UIView *subview in self.contentView.subviews) {
if ([subview isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)subview;
label.forbidSetBackgroundColor = NO;
}
}
}
[self ex_setHighlighted:highlighted animated:animated];
}
-
-
@end
原文:http://bbs.520it.com/forum.php?mod=viewthread&tid=2781 |