前言 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 |