移動端如何自定義文本選擇器顏色

在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)圖:

系統(tǒng)默認(rèn)效果

可以看到,當(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上也不起效。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評論 19 139
  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 4,068評論 1 22
  • 翻譯自“Auto Layout Guide”。 1 入門 1.1 理解自動布局 自動布局根據(jù)視圖層級結(jié)構(gòu)中視圖上的...
    lakerszhy閱讀 3,922評論 3 26
  • 太陽花,是過年的時候,老媽來給我們陽臺添置的花草中的一種。最近開始陸續(xù)開花了。今天早上拍到了太陽花開的全過程,用了...
    凱德印象閱讀 300評論 0 6
  • 優(yōu)美的詩歌,曼妙的音樂,擦亮每一個黎明,呵護(hù)每個生命。用詩歌浸潤每一個生命,為每一天注入力量的源泉?!对诿\無風(fēng)的...
    紅巧兒閱讀 273評論 0 0

友情鏈接更多精彩內(nèi)容