Пользовательское меню длительного нажатия WKWebView работает, но с некоторыми серьезными проблемами

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

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

На данный момент есть две проблемы:

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

  2. Если пользователь поднимает палец после того, как всплывающая анимация каким-то образом возникает, 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);
               }];
}

Во-вторых,

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, которое имеет значение ДА, когда был представлен контроллер предупреждений, и НЕТ, когда он был отклонен.

- (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:

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

В контроллере предупреждений Apple есть что-то, что не позволяет WKWebView перемещаться после того, как вы уберете палец.


person Vulkan    schedule 27.03.2017    source источник


Ответы (2)


Если я не ошибаюсь, во второй части:

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

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

person byJeevan    schedule 30.03.2017
comment
Я вставляю javascript, чтобы отключить системный диалог, когда страница завершает загрузку, а не когда начинается фаза распознавания жестов. Кроме того, это первая часть проблемы. - person Vulkan; 31.03.2017

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

Это такой ХАК.

person Vulkan    schedule 05.04.2017