Специальное меню длинных нажатий WKWebView работает, но с некоторыми серьезными проблемами

Когда пользователь долго нажимает на ссылку, появляется предупреждающий контроллер с параметрами:

  • Открыть
  • Открыть в новой вкладке
  • Копировать

В настоящее время существует две проблемы:

  • Если пользователь выполняет длинное нажатие до того, как WKWebView завершит навигацию, появится диспетчер предупреждений по умолчанию (Safari).

  • Если пользователь поднимает свой палец после появления всплывающей анимации, WKWebView каким-то образом регистрирует его как нажатие и переходит к этой ссылке, пока контрольный контроллер все еще отображается на экране.

В этот механизм входят три части.

Во-первых,

После того, как 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);
               }];
}

Во-вторых,

В WKWebView добавлен UILongPressGestureRecognizer и реализован так, что он находит атрибуты элементов на основе местоположения касания.

- (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, когда контроллер предупреждения был представлен, и НЕТ, когда он был уволен.

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

        decisionHandler(WKNavigationActionPolicyCancel);
    }
    else {

        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

Интересно, что в Интернете есть много примеров, где ссылки НЕ требуют политического решения. Они просто случаются, и я не могу остановить их.

Пример: http://www.dribbble.com

введите описание изображения здесь

Источник: 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:

Если я полностью удалю свою реализацию (все три шага) и позволю контроллеру предупреждений по умолчанию ударить каждый раз, когда я долгое время нажимаю ссылку, вторая проблема будет решена.

Что-то о контроллере предупреждений Apple, который предотвращает навигацию WKWebView после того, как вы поднимаете палец.

Ответ 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 ...
    }

Я еще не тестировал этот код. Было бы счастливее, если оно сработает.

Ответ 2

Этот ответ разрешит вторую проблему, но я не уверен, что это безопасно для 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.