美文网首页I love iOS猿故iOS开发攻城狮的集散地
移动端如何自定义文本选择器颜色

移动端如何自定义文本选择器颜色

作者: Code_Ninja | 来源:发表于2018-04-22 20:23 被阅读26次

    在web端要实现修改鼠标选中文本的颜色,只需要给CSS3的::selection选择器指定颜色就可以了。但是,如果你把加了同样的HTML页面用移动端的webView来加载,然后尝试用手指触摸来选择一段文本,你会发现选择器的颜色并没有发生改变,依然是系统的蓝色。

    这是为什么呢,查询资料发现移动端不支持::selection选择器。那如果我们还是想修改选择文本的颜色怎么办呢?

    让我们先来看看系统是的实现原理是怎么样子的,我们用webView加载一个HTML,然后利用Deubg View Hierarchy查看当前的视图层级结构,你会发现如下的层级结构图:

    系统默认效果

    可以看到,当我们选择一段文本的时候,系统会创建一个跟当前视图一样大小的UITextSelectionView覆盖到当前视图上,在其上面再创建一个UITextRangeView覆盖到当前选择文本的区域上。然后再到最上面一层,有三个UIView,每个UIView对应选择的一段文字,还有两个UISelectionGrabber和UISelectionGrabberDot是显示选择区域两边的线条和圆点。

    好,我们已经知道了系统的实现方式,但是UITextSelectionView、UISelectionGrabber和UISelectionGrabberDot都是系统私有的类,而且是在有选择的文本的时候才会创建出来,我们既没有直接访问它们的方法,也没法在它们被创建出来之后拿到它们来修改它们的属性。那我们怎么更改它们的颜色呢?答案是利用runtime的swizzleMethod技术,实现动态修改它们的颜色。

    经过我的尝试和实践发现:对于UITextSelectionView来说,只需要交换它的setBackGroundColor:方法,在系统给它设置背景色的时候,将其背景色修改为我们想要的颜色即可。而对于UISelectionGrabber和UISelectionGrabberDot,尝试了修改其背景色和tintColor均失败,猜测它们是通过绘图绘制的颜色。这里有个偷懒的做法是在其上面贴一个跟其大小一样的自定义视图。这里需要注意的是,UITextSelectionView、UISelectionGrabber和UISelectionGrabberDot应该是全局的懒加载对象,在需要的时候创建,并不会创建多次,添加自定义的子视图的时候应该避免重复添加。所以,需要交换willMoveToSuperview:方法,在视图将要被添加到父视图上的时候,判断其类型是UISelectionGrabber或UISelectionGrabberDot时,添加我们自定义颜色的子视图即可。

    这里我们把系统的蓝色修改为护眼一点的颜色,修改后的效果图如下:

    自定义效果

    不过,这种方式会把APP中所有文本选择器(UIWebView、UITextView等)的样式都改掉,比较暴力,如果要使用的话,建议检查下会不会影响其他地方的体验效果。通过以上思路,你是不是发现了改变其他系统类的样式的一种方式呢?

    下面附上实现代码:

    
    //
    
    //  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会覆盖父类方法的实现,但是不会替换父类已经存在的方法实现。如果要改变已经存在的方法实现,使用method_setImplementation。
    
        //这里只是尝试覆盖父类方法的实现,如果父类没有对应方法的实现,则覆盖成功,否则覆盖失败。
    
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
        if(didAddMethod){
    
            //如果要替换的方法存在,它调用的是class_addMethod。如果要替换的方法不存在,它调用的是method_setImplementation。
    
            //这里在覆盖父类方法成功的情况下,尝试用父类原有的方法的实现替换新增方法的实现。
    
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    
        }else{
    
            //这里在覆盖父类方法失败的情况下,交换两个两个方法的实现。
    
            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
    
    
    欢迎支持公众号猿故

    补充:经测试发现,WKWebView的文本选择器视图层级结构发生了很大的改变,而且实现方式也改了,所以目前这种方式在WKWebView上也不起效。

    相关文章

      网友评论

      • 番茄炒西红柿啊:没有这么麻烦的兄die,直接修改tintColor这个属性就好了.
        Code_Ninja:大兄die,你试过了嘛?我这里试过了,UIWebView设置tintColor并不能改变文字选中时的颜色效果。
      • 林川Verve:赞一个

      本文标题:移动端如何自定义文本选择器颜色

      本文链接:https://www.haomeiwen.com/subject/eqaclftx.html