在web端要實現(xiàn)修改鼠標(biāo)選中文本的顏色,只需要給CSS3的::selection選擇器指定顏色就可以了。但是,如果你把加了同樣的HTML頁面用移動端的webView來加載,然后嘗試用手指觸摸來選擇一段文本,你會發(fā)現(xiàn)選擇器的顏色并沒有發(fā)生改變,依然是系統(tǒng)的藍(lán)色。
這是為什么呢,查詢資料發(fā)現(xiàn)移動端不支持::selection選擇器。那如果我們還是想修改選擇文本的顏色怎么辦呢?
讓我們先來看看系統(tǒng)是的實現(xiàn)原理是怎么樣子的,我們用webView加載一個HTML,然后利用Deubg View Hierarchy查看當(dāng)前的視圖層級結(jié)構(gòu),你會發(fā)現(xiàn)如下的層級結(jié)構(gòu)圖:
可以看到,當(dāng)我們選擇一段文本的時候,系統(tǒng)會創(chuàng)建一個跟當(dāng)前視圖一樣大小的UITextSelectionView覆蓋到當(dāng)前視圖上,在其上面再創(chuàng)建一個UITextRangeView覆蓋到當(dāng)前選擇文本的區(qū)域上。然后再到最上面一層,有三個UIView,每個UIView對應(yīng)選擇的一段文字,還有兩個UISelectionGrabber和UISelectionGrabberDot是顯示選擇區(qū)域兩邊的線條和圓點。
好,我們已經(jīng)知道了系統(tǒng)的實現(xiàn)方式,但是UITextSelectionView、UISelectionGrabber和UISelectionGrabberDot都是系統(tǒng)私有的類,而且是在有選擇的文本的時候才會創(chuàng)建出來,我們既沒有直接訪問它們的方法,也沒法在它們被創(chuàng)建出來之后拿到它們來修改它們的屬性。那我們怎么更改它們的顏色呢?答案是利用runtime的swizzleMethod技術(shù),實現(xiàn)動態(tài)修改它們的顏色。
經(jīng)過我的嘗試和實踐發(fā)現(xiàn):對于UITextSelectionView來說,只需要交換它的setBackGroundColor:方法,在系統(tǒng)給它設(shè)置背景色的時候,將其背景色修改為我們想要的顏色即可。而對于UISelectionGrabber和UISelectionGrabberDot,嘗試了修改其背景色和tintColor均失敗,猜測它們是通過繪圖繪制的顏色。這里有個偷懶的做法是在其上面貼一個跟其大小一樣的自定義視圖。這里需要注意的是,UITextSelectionView、UISelectionGrabber和UISelectionGrabberDot應(yīng)該是全局的懶加載對象,在需要的時候創(chuàng)建,并不會創(chuàng)建多次,添加自定義的子視圖的時候應(yīng)該避免重復(fù)添加。所以,需要交換willMoveToSuperview:方法,在視圖將要被添加到父視圖上的時候,判斷其類型是UISelectionGrabber或UISelectionGrabberDot時,添加我們自定義顏色的子視圖即可。
這里我們把系統(tǒng)的藍(lán)色修改為護(hù)眼一點的顏色,修改后的效果圖如下:
不過,這種方式會把APP中所有文本選擇器(UIWebView、UITextView等)的樣式都改掉,比較暴力,如果要使用的話,建議檢查下會不會影響其他地方的體驗效果。通過以上思路,你是不是發(fā)現(xiàn)了改變其他系統(tǒng)類的樣式的一種方式呢?
下面附上實現(xiàn)代碼:
//
// UIView+AOP.m
// testPubb
//
// Created by lumin on 2018/4/21.
//
#import "UIView+AOP.h"
#import <objc/runtime.h>
@implementation UIView (AOP)
void swizzleMethod(Class class,SEL originalSelector,SEL swizzledSelector){
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//注意class_addMethod會覆蓋父類方法的實現(xiàn),但是不會替換父類已經(jīng)存在的方法實現(xiàn)。如果要改變已經(jīng)存在的方法實現(xiàn),使用method_setImplementation。
//這里只是嘗試覆蓋父類方法的實現(xiàn),如果父類沒有對應(yīng)方法的實現(xiàn),則覆蓋成功,否則覆蓋失敗。
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if(didAddMethod){
//如果要替換的方法存在,它調(diào)用的是class_addMethod。如果要替換的方法不存在,它調(diào)用的是method_setImplementation。
//這里在覆蓋父類方法成功的情況下,嘗試用父類原有的方法的實現(xiàn)替換新增方法的實現(xiàn)。
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
//這里在覆蓋父類方法失敗的情況下,交換兩個兩個方法的實現(xiàn)。
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//When Swizzling a instance method,use the following:
Class class = [self class];
//When Swizzling a class method, use the following:
// Class class = object_getClass((id)self);
swizzleMethod(class, @selector(setBackgroundColor:), @selector(aop_setBackgroundColor:));
swizzleMethod(class, @selector(willMoveToSuperview:), @selector(aop_willMoveToSuperview:));
});
}
- (void)aop_setBackgroundColor:(UIColor *)color
{
if([NSStringFromClass([self.superview.superview class])isEqualToString:@"UITextRangeView"]){
[self aop_setBackgroundColor:[UIColor colorWithRed:194/255.0 green:228/255.0 blue:193/255.0 alpha:0.5]];
}else{
[self aop_setBackgroundColor:color];
}
}
- (void)aop_willMoveToSuperview:(UIView *)view
{
NSString *className = NSStringFromClass([self class]);
if([className isEqualToString:@"UISelectionGrabber"] || [className isEqualToString:@"UISelectionGrabberDot"]){
UIView *coverView = [self viewWithTag:10000];
if(!coverView){
coverView = [[UIView alloc]initWithFrame:self.bounds];
coverView.tag = 10000;
[self addSubview:coverView];
}
if([className isEqualToString:@"UISelectionGrabberDot"]){
coverView.layer.cornerRadius = self.bounds.size.width * 0.5;
coverView.layer.masksToBounds = YES;
}
coverView.backgroundColor = [UIColor colorWithRed:194/255.0 green:228/255.0 blue:193/255.0 alpha:1.0];
}
[self aop_willMoveToSuperview:view];
}
@end
補充:經(jīng)測試發(fā)現(xiàn),WKWebView的文本選擇器視圖層級結(jié)構(gòu)發(fā)生了很大的改變,而且實現(xiàn)方式也改了,所以目前這種方式在WKWebView上也不起效。