最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - WKWebView custom long press menu works but with some major issues - Stack Overflow

programmeradmin2浏览0评论

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';"
               pletionHandler:^(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
                   pletionHandler:^(id result, NSError *error){

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

        [_webView evaluateJavaScript:[NSString stringWithFormat:@"MyAppGetHTMLElementsAtPoint(%f,%f);", touchLocation.x, touchLocation.y]
                   pletionHandler:^(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]
                                      pletionHandler:^(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]
                                      pletionHandler:^(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:

Source: /

Source 2:

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 pletely (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.

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';"
               pletionHandler:^(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
                   pletionHandler:^(id result, NSError *error){

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

        [_webView evaluateJavaScript:[NSString stringWithFormat:@"MyAppGetHTMLElementsAtPoint(%f,%f);", touchLocation.x, touchLocation.y]
                   pletionHandler:^(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]
                                      pletionHandler:^(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]
                                      pletionHandler:^(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.

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

Source 2: https://github./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 pletely (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.

Share Improve this question edited Mar 31, 2017 at 12:24 Vulkan asked Mar 27, 2017 at 18:35 VulkanVulkan 1,07617 silver badges47 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 1

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.

发布评论

评论列表(0)

  1. 暂无评论