简体   繁体   中英

WKWebView custom long press menu works but with some major issues

When the user long presses a link an alert controller appears with the options:

  • Open
  • Open in New Tab
  • Copy

There are two problems currently:

  1. If the user performs a long press before the WKWebView has finished the navigation the default (Safari's) alert controller appears.

  2. If the user lifts his finger after the popup animation occurs somehow the WKWebView registers it as a tap and navigates to that link while the alert controller is still displayed on screen.

There are three parts to this mechanism.

Firstly,

After the WKWebView has finished the navigation a javascript is injected to the page that disables the default alert controller.

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    [_webView evaluateJavaScript:@"document.body.style.webkitTouchCallout='none';"
               completionHandler:^(id result, NSError *error){

                   NSLog(@"Javascript: {%@, %@}", result, error.description);
               }];
}

Secondly,

A UILongPressGestureRecognizer is added to the WKWebView and implemented so that it finds the attributes of the elements based on the location of the touch.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

- (void)longPress:(UILongPressGestureRecognizer *)longPressGestureRecognizer
{
    if (longPressGestureRecognizer.state == UIGestureRecognizerStateBegan) {

        _shouldCancelNavigation = YES;

        CGPoint touchLocation = [longPressGestureRecognizer locationInView:_webView];

        NSString *javascript = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Javascript" ofType:@"js"]
                                                         encoding:NSUTF8StringEncoding
                                                            error:nil];

        [_webView evaluateJavaScript:javascript
                   completionHandler:^(id result, NSError *error){

                       NSLog(@"Javascript: {%@, %@}", result, error.description);
                   }];

        [_webView evaluateJavaScript:[NSString stringWithFormat:@"MyAppGetHTMLElementsAtPoint(%f,%f);", touchLocation.x, touchLocation.y]
                   completionHandler:^(id result, NSError *error){

                       NSLog(@"Javascript: {%@, %@}", result, error.description);

                       NSString *tags = (NSString *)result;

                       if ([tags containsString:@",A,"]) {

                           [_webView evaluateJavaScript:[NSString stringWithFormat:@"MyAppGetHREFAttributeAtPoint(%f,%f);", touchLocation.x, touchLocation.y]
                                      completionHandler:^(id result, NSError *error){

                                          NSLog(@"Javascript: {%@, %@}", result, error.description);

                                          NSString *urlString = (NSString *)result;

                                          [_delegate webView:self didLongPressAtTouchLocation:touchLocation URL:[NSURL URLWithString:urlString]];
                                      }];

                           return;
                       }

                       if ([tags containsString:@",IMG,"]) {

                           [_webView evaluateJavaScript:[NSString stringWithFormat:@"MyAppGetSRCAttributeAtPoint(%f,%f);", touchLocation.x, touchLocation.y]
                                      completionHandler:^(id result, NSError *error){

                                          NSLog(@"Javascript: {%@, %@}", result, error.description);

                                          NSString *urlString = (NSString *)result;

                                          [_delegate webView:self didLongPressAtTouchLocation:touchLocation imageWithSourceURL:[NSURL URLWithString:urlString]];
                                      }];

                           return;
                       }
                   }];
    }
}

Lastly,

The delegate method that presents the alert controller is implemented on the main ViewController.

My solution to the second problem has been to add a boolean value shouldCancelNavigation that is YES when the alert controller has been presented and NO when it has been dismissed.

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    if (_shouldCancelNavigation) {

        decisionHandler(WKNavigationActionPolicyCancel);
    }
    else {

        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

Interestingly enough there are many examples on the web where links DO NOT require a policy decision. They just happen with no way for me to stop them.

Example: http://www.dribbble.com

在此输入图像描述

Source: http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/comment-page-3/

Source 2: https://github.com/mozilla-mobile/firefox-ios/pull/61

EDIT :

This solves the 2nd problem but I'm not sure it won't break something somewhere else.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {

        otherGestureRecognizer.enabled = NO;

        otherGestureRecognizer.enabled = YES;
    }

    return YES;
}

EDIT 2 :

It does actually create a problem... You can no longer select text since the code above resets the internal long press gesture recognizers.

EDIT 3 :

If I remove my implementation completely (all 3 steps) and let the default alert controller kick in every time I long press a link the 2nd problem gets solved.

There's something about Apple's alert controller that prevents the WKWebView from navigating after you lift your finger.

If I am not wrong, In the second part :

- (void)longPress:(UILongPressGestureRecognizer *)longPressGestureRecognizer
{
    if (longPressGestureRecognizer.state == UIGestureRecognizerStateBegan) {

//Rest of your code ...
    }
}

You are injecting javascript to disable system dialog. Now after press ends, WKWebview already recieved event triggered from the web link. Since it is too late, why don't you try to check condition for longPressGestureRecognizer.state is equal to UIGestureRecognizerStateEnded instead.

Hence it changes to below code.

    if (longPressGestureRecognizer.state == UIGestureRecognizerStateEnded) {

       //Rest of your code ...
    }

I have not tested this code yet. Would be happier if it works.

This answer will solve the second problem but I'm not sure if it is safe for App Store.

First you will need to break the internal long press gesture recognizers so they do not fire when the user lifts his finger.

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {

        if (otherGestureRecognizer.state == UIGestureRecognizerStateBegan) {

            // Warning: This will break how WKWebView handles selection of text.

            [otherGestureRecognizer requireGestureRecognizerToFail:gestureRecognizer];
        }
    }

    return YES;
}

After the user has finished interacting with the custom long press menu this code will fix the broken WKWebView:

    [_webView removeGestureRecognizer:_longPressGestureRecognizer]; // This code will remove the dependency and recover the lost functionality.

    _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

    _longPressGestureRecognizer.numberOfTouchesRequired = 1;

    _longPressGestureRecognizer.delegate = self;

    [_webView addGestureRecognizer:_longPressGestureRecognizer];

It is such a HACK .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM