I love iOS猿故iOS开发攻城狮的集散地

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

2018-04-22  本文已影响26人  Code_Ninja

在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上也不起效。

上一篇下一篇

猜你喜欢

热点阅读