WKWebView自定义长按菜单有效,但有一些重大问题

时间:2017-03-27 18:35:53

标签: javascript ios objective-c wkwebview

当用户长按链接时,会出现一个警报控制器,其中包含以下选项:

  • 打开
  • 在新标签页中打开
  • 复制

目前有两个问题:

  1. 如果用户在WKWebView完成导航之前执行了长按,则会显示默认(Safari的)警报控制器。

  2. 如果用户在弹出动画发生后抬起手指,WKWebView会将其注册为点击并导航到该链接,同时警报控制器仍显示在屏幕上。

  3. 这种机制有三个部分。

    首先,

    在WKWebView完成导航后,会向禁用默认警报控制器的页面注入javascript。

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

    其次,

    UILongPressGestureRecognizer被添加到WKWebView并实现,以便根据触摸位置找到元素的属性。

    - (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;
                           }
                       }];
        }
    }
    

    最后,

    提供警报控制器的委托方法在主ViewController上实现。

    我对第二个问题的解决方案是添加一个布尔值shouldCancelNavigation,当警报控制器出现时为YES,而当它被解除时为NO。

    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
    {
        if (_shouldCancelNavigation) {
    
            decisionHandler(WKNavigationActionPolicyCancel);
        }
        else {
    
            decisionHandler(WKNavigationActionPolicyAllow);
        }
    }
    

    有趣的是,网络上有很多例子,其中链接不需要政策决定。它们只是让我无法阻止它们。

    示例:http://www.dribbble.com

    enter image description here

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

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

    修改

    这解决了第二个问题,但我不确定它是否会在其他地方破坏。

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
        if ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
    
            otherGestureRecognizer.enabled = NO;
    
            otherGestureRecognizer.enabled = YES;
        }
    
        return YES;
    }
    

    编辑2

    它确实会产生问题......由于上面的代码会重置内部长按手势识别器,因此您无法再选择文本。

    编辑3

    如果我完全删除我的实现(所有3个步骤)并且每次长按链接时让默认警报控制器启动,第二个问题就会解决。

    有关Apple的警报控制器的一些事情会阻止WKWebView在您举起手指后进行导航。

2 个答案:

答案 0 :(得分:1)

如果我没错,在第二部分:

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

//Rest of your code ...
    }
}

您正在注入javascript以禁用系统对话框。现在,在新闻结束后,WKWebview已经收到了从网络链接触发的事件。由于为时已晚,为什么不尝试检查longPressGestureRecognizer.state的条件是否等于UIGestureRecognizerStateEnded

因此它改为下面的代码。

    if (longPressGestureRecognizer.state == UIGestureRecognizerStateEnded) {

       //Rest of your code ...
    }

我还没有测试过这段代码。如果它有效会更快乐。

答案 1 :(得分:1)

这个答案将解决第二个问题,但我不确定App Store是否安全。

首先,您需要打破内部长按手势识别器,以便在用户抬起手指时不会触发。

- (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;
}

用户完成与自定义长按菜单的交互后,此代码将修复损坏的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];

这是 HACK