博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Runtime解决 cell 点击时子视图改变背景颜色的问题
阅读量:7025 次
发布时间:2019-06-28

本文共 6417 字,大约阅读时间需要 21 分钟。

hot3.png

前言

iOS 开发中,UITableView 随处可见,而在点击 UITableView 的 cell 的时候,如果他的子视图设置了透明颜色以外的颜色,子视图的背景颜色会进行相关的改变,效果如下图。

cell 被点击时,子视图的背景颜色会产生变化.gif

这种情况是不是有种似曾相识的感觉

如果没有,我再举几个很多人使用的 App 上对于这种情况处理不佳的例子,注意左右对比

新浪微博 cell 点击时消失的灰色分割线

简书 cell 点击时消失的蓝色小圈圈

产生这种情况的原因是因为 cell 在点击的时候会将子视图的背景颜色设置为透明色,而这里微博的分割线和简书的蓝色小圈圈应该是用了一种 UIView 设置了背景颜色来实现的,于是在点击过程中,本来是灰色的分割线,和本来是蓝色的小圈圈,都“消失”了。

网上对于这种情况的解决方案,大多是下面这种解决方式:

修改 cell 的选中样式:

 
  1.  
  2. cell.selectionStyle = UITableViewCellSelectionStyleNone;

这种解决方案,可以正确的达到点击的时候子视图背景颜色不再改变,只是美中不足的是,这种方法不仅去除了我们不想要的子视图背景颜色改变的效果,还去除了 cell 本身 contentView 的背景颜色改变的效果。

此时点击 cell ,用户不再会感觉到有任何变化,为了取消子视图背景颜色改变效果,而取消 cell 的选中效果,这种做法不太友好。

此时又有网友提出,自己实现这种选中效果,在 cell 的 contentView 的最下层添加一个 button,很好的思路,只是,如果此时你的 cell 已经使用 xib 布局的差不多了,或者使用纯代码写的差不多了,再向 cell 的 contentView 和你所添加的 subview 中间添加一个 button 就显得有点麻烦。

懒癌晚期,想有一劳永逸的解决办法,最好是那种不对原工程做任何改动的

下面是我思考的过程:

我先看看 cell 的 contentView 中所有子视图调用设置背景颜色的方法时的函数调用栈,看看颜色改变成透明眼色之前调用了哪些方法

cell 子视图更改颜色之前,究竟做了些什么?

我新建了一个 .m 文件,并在其中使用 runtime 的 Method Swizzle 对设置背景颜色方法进行替换,在新的设置背景颜色方法中打印函数调用栈,具体代码如下:

使用 runtime 打印设置背景颜色透明时的函数调用栈

这里如果不懂运行时的相关用法,可以自行去了解一下 runtime 的相关使用以及概念。

然后我们运行进行相关测试,发现打印了两次函数调用栈,意味着,在点击开始和结束之后,cell 的子视图有两次被设置成了透明颜色。其中一次是点击开始时,改变子视图背景颜色为透明色,还有一次是点击结束时,改变子视图背景颜色为透明色。

点击 cell 改变子视图背景颜色时的函数调用栈

取消点击 cell 时恢复子视图背景颜色时的函数调用栈

对比点击前后的函数调用栈,我们可以观察到,在点击 cell 的设置子视图背景颜色的前后都调用了

 
  1.  
  2. _setOpaque:forSubview:
  3. showSelectedBackgroundView:animated:
  4. setHighlighted:animated:

等方法;

其中:

  • 前两个方法是私有 API,使用私有 API 的结果无法预料。。可能会被苹果爸爸拒绝上线,所以,这里不考虑他们
  • 最后一个 setHighlighted:animated: 方法在点击时,传入的 highlighted 为 YES,点击结束传入的为 NO。似乎可以考虑在这里里一个 FLAG
  • 另外,在 cell 默认加载出来的时候也会调用 setHighlighted:animated: 方法,只不过传入的 highlighted 为 NO
  • 那么此处我已经确定可以用最后一个方法立一个 FLAG 了,也就是我在这里,只需要在 highlighted 传入为 YES 的时候让子视图禁止调用改变背景颜色的方法,然后在再次为 NO 的时候,恢复可以调用改变背景颜色。
  • 这似乎是一个可行的方法,但是,不要忘了,函数调用栈中的方法,是越早调用的方法越在下面,这里 setHighlighted:animated: 方法的调用在最后一次设置背景颜色为透明之前,也就是说,如果我按照上面的方法做了,那我就只能阻止一次设置背景颜色为透明
  • 我想到的解决方案,再立一个 FLAG,然后,在 highlighted 为 YES 的时候,让子视图禁止设置背景颜色,然后第一次设置为透明会被阻止,第二次的时候,判断 FLAG,通过 FLAG 再阻止下一次设置背景颜色为透明

禁止设置子视图背景颜色的具体代码如下:

禁止 UILabel 设置背景颜色

这段代码使用了 runtime 在交换的设置背景颜色方法中,对通过 runtime 添加的 forbidSetBackgroundColor 属性进行判断,如果设置为 YES,则不能设置背景颜色。

接下来对 cell 的 setHighlighted:animated: 进行替换,在合适的时机,设置 UILabel 能否改变背景颜色即可

具体代码如下:

替换 UITableViewCell 的 setHighlighted-animated- 方法

接下来,我们该立 FLAG 了

通过 Flag 阻止最后一次设置背景颜色为透明的操作

我们添加了一个新的属性,当 highlighted 为 YES 的时候,forbidSetBackgroundColor 会变成 YES,此时,FLAG:shouldIgnoreSetClearBackgroundColorHandle 设置为 YES,下一次 forbidSetBackgroundColor 不起作用的时候,通过 shouldIgnoreSetClearBackgroundColorHandle 可以再次阻止背景色被设置成透明色的操作。

写完这些代码,我想我以后又可以偷懒了,不需要以后每次去关心 cell 点击改变子视图背景颜色的问题,只需要新增一个 .m 文件,不需要改动原先一句代码,就可以实现点击 cell 前后,子视图颜色不会改变,而且也保留了原生的点击效果

不足之处

这篇文章中所实现的仅仅是 UITableViewCell 在点击过程中 UILabel 的颜色不会改变,其他的子控件,诸如,UIButton,UIView 之类的视图,背景色仍然会改变,而且也不能解决多个视图层叠的背景色被清空的情况,于是,我写了一个分类:

  • 实现了所有带有非透明颜色的 UI 控件在 UITableViewCell 中,点击过程中不会改变背景颜色的效果
  • 使用方法很简单,直接将分类拖入工程中,即可使用
  • 欢迎大家使用和 Star,欢迎提出修改意见,更欢迎提出 bug

感谢

在这里要感谢两个网友 angelen10 和 MuYanQin 对我所写的代码的 bug 提出,欢迎更多人指出我的 bug。

结尾

  • 最后附上 .m 文件所有的代码
  • 使用方法很简单,在工程中新增一个 .m 文件,把下方的所有代码粘贴上去
  • 不需要你调用和改变任何原先的代码,这段代码就会自动工作了,
  • 如果你有什么疑问,或者发现了我代码中有什么不对的地方,欢迎评论和纠错
  • 如果你想研究和阅读本篇文章测试所写的工程,可以到我的 GitHub: 上下载
 
  1.  
  2. #import <UIKit/UIKit.h>
  3. #import <objc/runtime.h>
  4.  
  5. UILabel (RuntimeTest)
  6.  
  7. (nonatomic, assign) BOOL forbidSetBackgroundColor;
  8.  
  9. (nonatomic, assign) BOOL shouldIgnoreSetClearBackgroundColorHandle;
  10.  
  11.  
  12. @implementation UILabel (RuntimeTest)
  13.  
  14. + (void)load {
  15.  
  16. SEL originalSelector = @selector(setBackgroundColor:);
  17. SEL swizzledSelector = @selector(ex_setBackgroundColor:);
  18.  
  19. Method originalMethod = class_getInstanceMethod(self, originalSelector);
  20. Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
  21.  
  22. BOOL success = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
  23. if (success) {
  24. class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
  25. } else {
  26. method_exchangeImplementations(originalMethod, swizzledMethod);
  27. }
  28. }
  29.  
  30. - (void)ex_setBackgroundColor:(UIColor *)backgroundColor {
  31.  
  32. if (self.forbidSetBackgroundColor){
  33. self.shouldIgnoreSetClearBackgroundColorHandle = YES;
  34. } else {
  35. if (backgroundColor == [UIColor clearColor]) {
  36. if (self.shouldIgnoreSetClearBackgroundColorHandle) {
  37. self.shouldIgnoreSetClearBackgroundColorHandle = NO;
  38. } else {
  39. [self ex_setBackgroundColor:backgroundColor];
  40. }
  41. } else {
  42. [self ex_setBackgroundColor:backgroundColor];
  43. }
  44. }
  45. }
  46.  
  47. - (BOOL)shouldIgnoreSetClearBackgroundColorHandle {
  48.  
  49. id value = objc_getAssociatedObject(self, _cmd);
  50. if (value == nil) {
  51. objc_setAssociatedObject(self, _cmd, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  52. }
  53. return [objc_getAssociatedObject(self, _cmd) boolValue];
  54. }
  55.  
  56. - (void)setShouldIgnoreSetClearBackgroundColorHandle:(BOOL)shouldIgnoreSetClearBackgroundColorHandle {
  57.  
  58. objc_setAssociatedObject(self, @selector(shouldIgnoreSetClearBackgroundColorHandle), @(shouldIgnoreSetClearBackgroundColorHandle), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  59. }
  60.  
  61. - (BOOL)forbidSetBackgroundColor {
  62.  
  63. id value = objc_getAssociatedObject(self, _cmd);
  64. if (value == nil) {
  65. objc_setAssociatedObject(self, _cmd, @(NO), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  66. }
  67. return [objc_getAssociatedObject(self, _cmd) boolValue];
  68. }
  69.  
  70. - (void)setForbidSetBackgroundColor:(BOOL)forbidSetBackgroundColor {
  71.  
  72. objc_setAssociatedObject(self, @selector(forbidSetBackgroundColor), @(forbidSetBackgroundColor), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  73. }
  74.  
  75. @end
  76.  
  77. @implementation UITableViewCell (RuntimeTest)
  78.  
  79. + (void)load {
  80.  
  81. SEL originalSelector = @selector(setHighlighted:animated:);
  82. SEL swizzledSelector = @selector(ex_setHighlighted:animated:);
  83.  
  84. Method originalMethod = class_getInstanceMethod(self, originalSelector);
  85. Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
  86.  
  87. BOOL success = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
  88. if (success) {
  89. class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
  90. } else {
  91. method_exchangeImplementations(originalMethod, swizzledMethod);
  92. }
  93. }
  94.  
  95. - (void)ex_setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
  96.  
  97. if (highlighted == YES) {
  98. for (UIView *subview in self.contentView.subviews) {
  99. if ([subview isKindOfClass:[UILabel class]]) {
  100. UILabel *label = (UILabel *)subview;
  101. label.forbidSetBackgroundColor = YES;
  102. }
  103. }
  104. } else {
  105. for (UIView *subview in self.contentView.subviews) {
  106. if ([subview isKindOfClass:[UILabel class]]) {
  107. UILabel *label = (UILabel *)subview;
  108. label.forbidSetBackgroundColor = NO;
  109. }
  110. }
  111. }
  112. [self ex_setHighlighted:highlighted animated:animated];
  113. }
  114.  
  115.  
  116. @end

 

 

原文:http://bbs.520it.com/forum.php?mod=viewthread&tid=2781

转载于:https://my.oschina.net/u/2345393/blog/791468

你可能感兴趣的文章
[下载地址] eclipse下载
查看>>
第一次作业+105032014098
查看>>
Codeforces 832B: Petya and Exam
查看>>
axios链接带参数_VUE升级(全面解析vuecil3/vuecil4的vue.config.js等常用配置,配置axios)...
查看>>
vue warning如何去掉_详解vue组件三大核心概念
查看>>
qt mysql md5加密_Qt 给密码进行MD5加密
查看>>
用java swing做连连看_java基于swing实现的连连看代码
查看>>
java关键字定义字符变量_Java 关键字和标识符
查看>>
java并发编程核心方法与框架_Java并发编程核心方法与框架-CompletionService的使用...
查看>>
java开源api网关_常用的几个开源 API网关管理系统
查看>>
java 数据结构 快速入门_Java 数据结构
查看>>
mysql 事务 隔离级别_MySQL中四种事务隔离级别详解
查看>>
leetcode 459 java_【leetcode刷题】[简单]459. 重复的子字符串(repeated substring pattern)-java...
查看>>
opencv java 轮廓提取_Opencv处理图像之轮廓提取
查看>>
java离用户最近的地区_解决浮动带来的影响、溢出属性、定位、验证浮动和定位是否脱离文档流、z-index模态框、透明度opacity、JavaScript编程语言开头...
查看>>
java final 域 安全性_安卓开发(Java)中关于final关键字与线程安全性
查看>>
java 1.8 tar.gz_CentOS配置JDK1.8(tar.gz)
查看>>
java 弹出窗口在屏幕中心_屏幕上的弹出式窗口中央?
查看>>
php 类中 全局变量,类名称中的PHP命名空间和全局变量问题
查看>>
php 封装一个sdk,PHP sdk实现在线打包代码示例
查看>>