Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WKWebView - prevent automatic scrolling triggered by user text selection

When a user performs a tap and hold gesture to select a word and then drags their finger towards either the top or bottom edges of the screen, the page automatically scrolls in order to accommodate the selection.

here is a short clip demonstrating it

I would like to prevent this behavior inside a WKWebView.

Here is what I have tried so far:

in a bridge.js file which is accessible to the webview:

var shouldAllowScrolling = true;

document.addEventListener('selectionchange', e => {
    shouldAllowScrolling = getSelectedText().length === 0;
    window.webkit.messageHandlers.selectionChangeHandler.postMessage(
        {
            shouldAllowScrolling: shouldAllowScrolling
        });
    console.log('allow scrolling = ', shouldAllowScrolling);
});

and then in a WKScriptMessageHandler implementation:

public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
    {
        switch message.name
        {
        case "selectionChangeHandler":
            let params = paramsDictionary(fromMessageBody: message.body)
            let shouldEnableScrolling = params["shouldAllowScrolling"] as? Bool ?? true
            cell?.webView.scrollView.isScrollEnabled = shouldEnableScrolling
            cell?.webView.scrollView.isUserInteractionEnabled = shouldEnableScrolling // not together with the line above 
        default:
            fatalError("\(#function): received undefined message handler name: \(message.name)")
        }
    }

Similarly, I have tried calling the preventDefault() function directly in the javascript file for a bunch of events, namely scroll and touchmove, like so:

document.addEventListener('touchmove', e => {
    if (!shouldAllowScrolling) {
        e.preventDefault()
    }
}, {passive: false});

both methods successfully prevent scrolling when some text is selected but do not override the behavior described at the very top of my question.

I can accept solutions in either Swift and JavaScript or a mix of both.

like image 211
banana1 Avatar asked Mar 05 '20 14:03

banana1


1 Answers

I ended up solving this problem by saving the last scroll position and scrolling to it when appropriate, like so:

var shouldAllowScrolling = true;
var lastSavedScrollLeft = 0;
var lastSavedScrollTop = 0;

function saveScrollPosition() {
    lastSavedScrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
    lastSavedScrollTop = window.pageYOffset || document.documentElement.scrollTop;
}

document.addEventListener('touchstart', e => {
    saveScrollPosition();
});

document.addEventListener('touchend', () => {
    // enable scrolling when the user lifts their finger, to allow scrolling while text selection is still present
    shouldAllowScrolling = true;
});

document.addEventListener('scroll', e => {
    if (!shouldAllowScrolling) {
        window.scrollTo(lastSavedScrollLeft, lastSavedScrollTop);
    }
});

document.addEventListener('selectionchange', e => {
    shouldAllowScrolling = getSelectedText().length === 0;
});

If someone can offer a more elegant solution that prevents the scrolling entirely ill be happy to accept it.

EDIT:

this solution may cause light shaking/jittering.

that can be solved by disabling the scroll natively while shouldAllowScrolling is set to false, like so:

webView.scrollView.isScrollEnabled = false
like image 93
banana1 Avatar answered Nov 15 '22 11:11

banana1