Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change the text selection color in WKWebView?

I'm trying to change text selection color in WKWebView. I tried pretty much all the solutions proposed for UIWebView/WKWebView that I was able to find.

Changing tint color does not work. Applying css for ::selection, -webkit-tap-highlight-color tags works neither.

enter image description here

It's always blue. Is it possible to change it?

like image 940
Rafa de King Avatar asked Aug 01 '17 11:08

Rafa de King


3 Answers

As of iOS 13, setting WKWebView's tintColor property also changes the selection color (and the caret color).

WKWebView *webView = ...
webView.tintColor = UIColor.redColor;

Extra tip: if you have an app that has dark mode support but for some reason the WKWebView content must be light mode, you can either force the whole view controller containing the WKWebView to have the light mode trait or you can do:

if (@available(iOS 13.0, *)) {
    webView.tintColor = [webView.tintColor resolvedColorWithTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]];
}

this ensures that the selection and caret colors will be visible with "light" content in the html

like image 183
txulu Avatar answered Sep 21 '22 18:09

txulu


Here's the sample code for it.

function highlight(colour) {
var range, sel;
if (window.getSelection) {
    // IE9 and non-IE
    try {
        if (!document.execCommand("BackColor", false, colour)) {
            makeEditableAndHighlight(colour);
        }
    } catch (ex) {
        makeEditableAndHighlight(colour)
    }
} else if (document.selection && document.selection.createRange) {
    // IE <= 8 case
    range = document.selection.createRange();
    range.execCommand("BackColor", false, colour);
}}

call this method by Objective-C code

[webView stringByEvaluatingJavaScriptFromString:@"highlight('#ff0')"];
like image 24
Kirti Parghi Avatar answered Sep 21 '22 18:09

Kirti Parghi


enter image description here

Here's my shot at private WebKit API hacky swizzling obviously not eligible for an AppStore app. Apple happens to provide lots of open source code for it: https://opensource.apple.com/source/WebKit2/WebKit2-7601.1.46.9/UIProcess/ios/WKContentViewInteraction.h.auto.html https://opensource.apple.com/source/WebKit2/WebKit2-7601.1.46.9/UIProcess/ios/WKContentViewInteraction.mm.auto.html

So the actual selection highlight for html content happens in 2 phases. Until the tap is being held on the touch screen private class UIWKSelectionView is shown. The highlight happens to be its tintView property. Each tintView getter call generates a new UIView instance with highlight color as the background. So the override needs to happen after each access.

In the second phase (after user releases the tap) the selected range is represented by private class UIWebTextRangeView. The text vertical markers with dots are UIWebDragDotView. The highlighting happens in updateRectViews method , which needs to be called before doing the color overriding.

The final solution is validated for iOS 8 - 11, once the color is overriden it will affect all WKWebView instances. The original highlight color is hardcoded in UIWKSelectionView & UIWebTextRangeView and fetched through UIKit private method +[UIColor selectionHighlightColor] which yields RGBA 0 0.33 0.65 0.2.

The actual code (I chose obj-c for swizzling convenience but this could also be done in Swift):

#import "ViewController.h"
#import <objc/runtime.h>

@import WebKit;
static IMP __original_Method_IMP_tintView;
static IMP __original_Method_IMP_updateRectViews;

//UIWebTextRangeView
void replacement_updateRectViews(UIView* self, SEL _cmd)
{
    ((void(*)(id,SEL))__original_Method_IMP_updateRectViews)(self, _cmd);
    for (UIView* view in self.subviews) {
        //isMemberOfClass would be used instead to filter out UIWebDragDotView if its color is meant to be unchanged
        if ([view isKindOfClass:NSClassFromString(@"UIWebDragDotView")]) {
            [view setValue:UIColor.redColor forKey:@"m_selectionBarColor"];
        } else {
            //These are UIView*
            view.backgroundColor = [UIColor colorWithRed:1.0 green:0 blue:0 alpha:0.2];
        }
    }
}

//UIWKSelectionView
UIView* replacement_tintView(id self, SEL _cmd)
{
    UIView* tintView = ((UIView*(*)(id,SEL))__original_Method_IMP_tintView)(self, _cmd);
    tintView.backgroundColor = [UIColor colorWithRed:1.0 green:0 blue:0 alpha:0.2]; 
    return tintView;
}

@interface ViewController ()
@end

@implementation ViewController
+ (void)load {
    __original_Method_IMP_tintView = method_setImplementation(class_getInstanceMethod(NSClassFromString(@"UIWKSelectionView"),NSSelectorFromString(@"tintView")), (IMP)replacement_tintView);
    __original_Method_IMP_updateRectViews = method_setImplementation(class_getInstanceMethod(NSClassFromString(@"UIWebTextRangeView"),NSSelectorFromString(@"updateRectViews")), (IMP)replacement_updateRectViews);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view = [WKWebView new];
    [(WKWebView*)self.view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://stackoverflow.com/"]]];
}
@end
like image 21
Kamil.S Avatar answered Sep 21 '22 18:09

Kamil.S