title: WKWebView custom long press menu works but with some major issues
body: <p>When the user long presses a link an alert controller appears with the options:</p>
  
  <ul>
  <li>Open</li>
  <li>Open in New Tab</li>
  <li>Copy</li>
  </ul>
  
  <p>There are two problems currently:</p>
  
  <ol>
  <li><p>If the user performs a long press before the WKWebView has finished the navigation the default (Safari's) alert controller appears.</p></li>
  <li><p>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.</p></li>
  </ol>
  
  <p>There are three parts to this mechanism.</p>
  
  <p>Firstly,</p>
  
  <p>After the WKWebView has finished the navigation a javascript is injected to the page that disables the default alert controller.</p>
  
  <pre><code>- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
  {
      [_webView evaluateJavaScript:@"document.body.style.webkitTouchCallout='none';"
                 completionHandler:^(id result, NSError *error){
  
                     NSLog(@"Javascript: {%@, %@}", result, error.description);
                 }];
  }
  </code></pre>
  
  <p>Secondly, </p>
  
  <p>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.</p>
  
  <pre><code>- (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;
                         }
                     }];
      }
  }
  </code></pre>
  
  <p>Lastly,</p>
  
  <p>The delegate method that presents the alert controller is implemented on the main ViewController.</p>
  
  <p>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.</p>
  
  <pre><code>- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
  {
      if (_shouldCancelNavigation) {
  
          decisionHandler(WKNavigationActionPolicyCancel);
      }
      else {
  
          decisionHandler(WKNavigationActionPolicyAllow);
      }
  }
  </code></pre>
  
  <p>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.</p>
  
  <p>Example: <a href="http://www.dribbble.com" rel="noreferrer">http://www.dribbble.com</a></p>
  
  <p><a href="https://i.stack.imgur.com/XxpyZ.png" rel="noreferrer"><img src="https://i.stack.imgur.com/XxpyZ.png" alt="enter image description here"></a></p>
  
  <p>Source: <a href="http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/comment-page-3/" rel="noreferrer">http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/comment-page-3/</a></p>
  
  <p>Source 2: <a href="https://github.com/mozilla-mobile/firefox-ios/pull/61" rel="noreferrer">https://github.com/mozilla-mobile/firefox-ios/pull/61</a></p>
  
  <p><strong>EDIT</strong>:</p>
  
  <p>This solves the 2nd problem but I'm not sure it won't break something somewhere else.</p>
  
  <pre><code>- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  {
      if ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
  
          otherGestureRecognizer.enabled = NO;
  
          otherGestureRecognizer.enabled = YES;
      }
  
      return YES;
  }
  </code></pre>
  
  <p><strong>EDIT 2</strong>:</p>
  
  <p>It does actually create a problem... You can no longer select text since the code above resets the internal long press gesture recognizers.</p>
  
  <p><strong>EDIT 3</strong>:</p>
  
  <p>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.</p>
  
  <p>There's something about Apple's alert controller that prevents the WKWebView from navigating after you lift your finger.</p>