UIWebView - включение таблиц действий в тегах <img>

Является ли это только мной или ли панель действий в тегах <img> отключена в UIWebView? В Safari, например, когда вы хотите сохранить изображение локально, вы касаетесь и удерживаете изображение, чтобы отобразить отображаемый лист действий. Но он не работает в моем пользовательском интерфейсе UIWebView. Я имею в виду, что он все еще работает для тегов <a>, т.е. Когда я касаюсь и удерживаю ссылки html, появляется лист действий. Но не для тегов <img>.

Я пробовал такие вещи, как put img { -webkit-touch-callout: inherit; } в css, который не работал. С другой стороны, когда я дважды нажимаю и удерживаю изображения, появляется всплывающее окно с копией.

Итак, вопрос в том, что для UIWebView отключена выноска листа действия по умолчанию для тегов <img>? Так, есть ли способ снова включить его? Я googled вокруг и видел много Q & Как о том, как отключить его в UIWebView, так это только я, кто не видит всплывающее окно?

Спасибо заранее!

Ответы

Ответ 1

Да, Apple отключила эту функцию (среди прочих) в UIWebViews и сохранила ее только для Safari.

Однако вы можете воссоздать это самостоятельно, расширив этот учебник, http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/.

Как только вы закончите этот учебник, вы захотите добавить несколько дополнительных, чтобы вы могли фактически сохранять изображения (которые учебник не охватывает). Я добавил дополнительное уведомление под названием @ "tapAndHoldShortNotification" через 0,3 секунды, которое вызывает метод с только отключенным кодом выноска в нем (чтобы предотвратить по умолчанию и ваше собственное меню, когда страница по-прежнему загружается, небольшое исправление ошибки).

Также для обнаружения изображений вам нужно расширить JSTools.js, здесь мои дополнительные функции.

function MyAppGetHTMLElementsAtPoint(x,y) {
    var tags = ",";
    var e = document.elementFromPoint(x,y);
    while (e) {
        if (e.tagName) {
            tags += e.tagName + ',';
        }
        e = e.parentNode;
    }
    return tags;
}

function MyAppGetLinkSRCAtPoint(x,y) {
    var tags = "";
    var e = document.elementFromPoint(x,y);
    while (e) {
        if (e.src) {
            tags += e.src;
            break;
        }
        e = e.parentNode;
    }
    return tags;
}

function MyAppGetLinkHREFAtPoint(x,y) {
    var tags = "";
    var e = document.elementFromPoint(x,y);
    while (e) {
        if (e.href) {
            tags += e.href;
            break;
        }
        e = e.parentNode;
    }
    return tags;
}

Теперь вы можете обнаружить, что пользователь нажимает на изображения и фактически обнаруживает URL-адрес изображения, на который они нажимают, но нам нужно изменить метод openContextualMenuAtPoint: - (void), чтобы предоставить дополнительные параметры.

Снова здесь моя (я попытался скопировать поведение Safari для этого):

- (void)openContextualMenuAt:(CGPoint)pt{
    // Load the JavaScript code from the Resources and inject it into the web page
    NSString *path = [[NSBundle mainBundle] pathForResource:@"JSTools" ofType:@"js"];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    [webView stringByEvaluatingJavaScriptFromString:jsCode];

    // get the Tags at the touch location
    NSString *tags = [webView stringByEvaluatingJavaScriptFromString:
                      [NSString stringWithFormat:@"MyAppGetHTMLElementsAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];

    NSString *tagsHREF = [webView stringByEvaluatingJavaScriptFromString:
                          [NSString stringWithFormat:@"MyAppGetLinkHREFAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];

    NSString *tagsSRC = [webView stringByEvaluatingJavaScriptFromString:
                         [NSString stringWithFormat:@"MyAppGetLinkSRCAtPoint(%i,%i);",(NSInteger)pt.x,(NSInteger)pt.y]];



    UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];

    selectedLinkURL = @"";
    selectedImageURL = @"";

    // If an image was touched, add image-related buttons.
    if ([tags rangeOfString:@",IMG,"].location != NSNotFound) {
        selectedImageURL = tagsSRC;

        if (sheet.title == nil) {
            sheet.title = tagsSRC;
        }

        [sheet addButtonWithTitle:@"Save Image"];
        [sheet addButtonWithTitle:@"Copy Image"];
    }
    // If a link is pressed add image buttons.
    if ([tags rangeOfString:@",A,"].location != NSNotFound){
        selectedLinkURL = tagsHREF;

        sheet.title = tagsHREF;
        [sheet addButtonWithTitle:@"Open"];
        [sheet addButtonWithTitle:@"Copy"];
    }

    if (sheet.numberOfButtons > 0) {
        [sheet addButtonWithTitle:@"Cancel"];
        sheet.cancelButtonIndex = (sheet.numberOfButtons-1);
        [sheet showInView:webView];
    }
    [selectedLinkURL retain];
    [selectedImageURL retain];
    [sheet release];
}

(ПРИМЕЧАНИЯ: selectedLinkURL и selectedImageURL объявлены в файле .h, чтобы позволить им получить доступ во всем классе, для сохранения или открытия последней ссылки.

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

Чтобы обработать выбор пользователей, теперь нам нужно добавить actionSheet: clickedButtonAtIndex: method.

-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
    if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:@"Open"]){
        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:selectedLinkURL]]];
    }
    else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:@"Copy"]){
        [[UIPasteboard generalPasteboard] setString:selectedLinkURL];
    }
    else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:@"Copy Image"]){
        [[UIPasteboard generalPasteboard] setString:selectedImageURL];
    }
    else if ([[actionSheet buttonTitleAtIndex:buttonIndex] isEqualToString:@"Save Image"]){
        NSOperationQueue *queue = [NSOperationQueue new];
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(saveImageURL:) object:selectedImageURL];
        [queue addOperation:operation];
        [operation release];
    }
}

Это проверяет, что пользователь хочет делать и обрабатывает/большинство/из них, только операция "сохранить изображение" нуждается в другом методе для этого. Для прогресса я использовал MBProgressHub. Добавьте MBProgressHUB * progressHud; к объявлению интерфейса в .h и установить его в методе init (из любого класса, с которым вы обрабатываете веб-просмотр).

    progressHud = [[MBProgressHUD alloc] initWithView:self.view];
    progressHud.customView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Tick.png"]] autorelease];
    progressHud.opacity = 0.8;
    [self.view addSubview:progressHud];
    [progressHud hide:NO];
    progressHud.userInteractionEnabled = NO;

И - (void) saveImageURL: (NSString *) url; метод фактически сохранит его в библиотеке изображений. (Лучше всего было бы выполнить загрузку через NSURLRequest и обновить прогресс в MBProgressHUDModeDeterminate, чтобы отклонить, сколько времени это займет на самом деле для загрузки, но это более сложная реализация, чем это)

-(void)saveImageURL:(NSString*)url{
    [self performSelectorOnMainThread:@selector(showStartSaveAlert) withObject:nil waitUntilDone:YES];
    UIImageWriteToSavedPhotosAlbum([UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]], nil, nil, nil);
    [self performSelectorOnMainThread:@selector(showFinishedSaveAlert) withObject:nil waitUntilDone:YES];
}
-(void)showStartSaveAlert{
    progressHud.mode = MBProgressHUDModeIndeterminate;
    progressHud.labelText = @"Saving Image...";
    [progressHud show:YES];
}
-(void)showFinishedSaveAlert{
    // Set custom view mode
    progressHud.mode = MBProgressHUDModeCustomView;
    progressHud.labelText = @"Completed";
    [progressHud performSelector:@selector(hide:) withObject:[NSNumber numberWithBool:YES] afterDelay:0.5];
}

И по причине добавить [progressHud release]; к методу dealloc.

Надеюсь, это покажет вам, как добавить некоторые из параметров веб-браузера, которые осталось в яблоке. Из-за причины, вы можете добавить к этому дополнительные вещи, например, вариант "Читать позже" для instapaper или кнопку "Open In Safari". (глядя на длину этого сообщения, я вижу, почему исходный учебник не учитывал детали финальной реализации)

Изменить: (обновлено с дополнительной информацией)

Меня спросили о деталях, которые я заглянул наверху, @ "tapAndHoldShortNotification", поэтому это разъясняет его.

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

- (void)tapAndHoldAction:(NSTimer*)timer {
    contextualMenuTimer = nil;
    UIView* clickedView = [self hitTest:CGPointMake(tapLocation.x, tapLocation.y) withEvent:nil];
    while (clickedView != nil) {
        if ([clickedView isKindOfClass:[UIWebView class]]) {
            break;
        }
        clickedView = clickedView.superview;
    }

    if (clickedView) {
        NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
                               [NSNumber numberWithFloat:tapLocation.x],@"x",
                               [NSNumber numberWithFloat:tapLocation.y],@"y",nil];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"TapAndHoldNotification" object:coord];
    }
}
- (void)tapAndHoldActionShort:(NSTimer*)timer {
    UIView* clickedView = [self hitTest:CGPointMake(tapLocation.x, tapLocation.y) withEvent:nil];
    while (clickedView != nil) {
        if ([clickedView isKindOfClass:[UIWebView class]]) {
            break;
        }
        clickedView = clickedView.superview;
    }

    if (clickedView) {
        NSDictionary *coord = [NSDictionary dictionaryWithObjectsAndKeys:
                               [NSNumber numberWithFloat:tapLocation.x],@"x",
                               [NSNumber numberWithFloat:tapLocation.y],@"y",nil];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"TapAndHoldShortNotification" object:coord];
    }
}

- (void)sendEvent:(UIEvent *)event {
    NSSet *touches = [event touchesForWindow:self];
    [touches retain];

    [super sendEvent:event];    // Call super to make sure the event is processed as usual

    if ([touches count] == 1) { // We're only interested in one-finger events
        UITouch *touch = [touches anyObject];

        switch ([touch phase]) {
            case UITouchPhaseBegan:  // A finger touched the screen
                tapLocation = [touch locationInView:self];
                [contextualMenuTimer invalidate];
                contextualMenuTimer = [NSTimer scheduledTimerWithTimeInterval:0.8 target:self selector:@selector(tapAndHoldAction:) userInfo:nil repeats:NO];
                NSTimer *myTimer;
                myTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(tapAndHoldActionShort:) userInfo:nil repeats:NO];
                break;

            case UITouchPhaseEnded:
            case UITouchPhaseMoved:
            case UITouchPhaseCancelled:
                [contextualMenuTimer invalidate];
                contextualMenuTimer = nil;
                break;
        }
    } else {        // Multiple fingers are touching the screen
        [contextualMenuTimer invalidate];
        contextualMenuTimer = nil;
    }
    [touches release];
}

Затем уведомление обрабатывается следующим образом:

// in -viewDidLoad

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(stopSelection:) name:@"TapAndHoldShortNotification" object:nil];


- (void)stopSelection:(NSNotification*)notification{
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitTouchCallout='none';"];
}

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

Кроме того, вы можете легко добавить поддержку iPad, отправив местоположение касаний с оповещением об уведомлении, а затем отобразив UIActionSheet с этой точки, хотя это было написано перед iPad, поэтому не включает в себя поддержку для этого.

Ответ 2

После битвы, как и 2 или 3 дня без остановки по этой проблеме, кажется, что позиция вычисляется относительно относительно UIWebView "TOP-LEFT" (я программирую для iOS 7).

Итак, чтобы сделать эту работу, когда вы получите позицию, на контроллере, где находится ваш WebView (я поставлю фрагмент кода ниже), не добавляйте "scroll-offset"

SNIPPET - ContextualMenuAction:

- (void)contextualMenuAction:(NSNotification*)notification {
// Load javascript
[self loadJavascript];

// Initialize the coordinates
CGPoint pt;
pt.x = [[[notification object] objectForKey:@"x"] floatValue];
pt.y = [[[notification object] objectForKey:@"y"] floatValue];

// Convert point from window to view coordinate system
pt = [self.WebView convertPoint:pt fromView:nil];

// Get PAGE and UIWEBVIEW dimensions
CGSize pageDimensions = [self.WebView documentSize];
CGSize webviewDimensions = self.WebView.frame.size;

/***** If the page is in MOBILE version *****/
if (webviewDimensions.width == pageDimensions.width) {

}

/***** If the page is in DESKTOP version *****/
else {
    // convert point from view to HTML coordinate system
    CGSize viewSize = [self.WebView frame].size;
    // Contiens la portion de la page visible depuis la webview (en fonction du zoom)
    CGSize windowSize = [self.WebView windowSize];

    CGFloat factor = windowSize.width / viewSize.width;
    CGFloat factorHeight = windowSize.height / viewSize.height;
    NSLog(@"factor: %f", factor);
        pt.x = pt.x * factor; // ** logically, we would add the offset **
        pt.y = pt.y * factorHeight; // ** logically, we would add the offset **
}

NSLog(@"x: %f and y: %f", pt.x, pt.y);
NSLog(@"WINDOW: width: %f height: %f", [self.WebView windowSize].width, [self.WebView windowSize].height);
NSLog(@"DOCUMENT: width: %f height: %f", pageDimensions.width, pageDimensions.height);
[self openContextualMenuAt:pt];
}

SNIPPET - в openContextualMenuAt:

Чтобы загрузить правильную функцию JS:

- (void)openContextualMenuAt:(CGPoint)pt {
    // Load javascript
    [self loadJavascript];

    // get the Tags at the touch location
    NSString *tags = [self.WebView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"getHTMLTagsAtPoint(%li,%li);",(long)pt.x,(long)pt.y]];
    ...
}

SNIPPET - в JSTools.js:

Это функция, которую я использую, чтобы получить элемент, затронутый

function getHTMLTagsAtPoint(x,y) {
    var tags = ",";
    var element = document.elementFromPoint(x,y);
    while (element) {
        if (element.tagName) {
            tags += element.tagName + ',';
        }
        element = element.parentNode;
    }
    return tags;
}

SNIPPET - loadJavascript

Я использую этот код для ввода моего JS-кода в webview

-(void)loadJavascript {
    [self.WebView stringByEvaluatingJavaScriptFromString:
    [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"JSTools" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]];
}

Эта часть (все, что я сделал, чтобы переопределить таблицу UIActionSheet по умолчанию) HEAVILY (я должен сказать полностью) на основе этот пост @Freerunning answer is complete (я сделал почти все, что он сказал в моих других классах, например, на том, на котором основан мой код), опубликованные фрагменты - это просто показать вам "полностью", как мой код.

Надеюсь, это поможет! ^^

Ответ 3

Прежде всего благодаря Freerunnering для отличного решения!

Но вы можете сделать это с помощью UILongPressGestureRecognizer вместо пользовательского LongPressRecognizer. Это упрощает реализацию:

В Viewcontroller, содержащем webView:

Добавить UIGestureRecognizerDelegate в ваш ViewController

let mainJavascript = "function MyAppGetHTMLElementsAtPoint(x,y) { var tags = \",\"; var e = document.elementFromPoint(x,y); while (e) { if (e.tagName) { tags += e.tagName + ','; } e = e.parentNode; } return tags; } function MyAppGetLinkSRCAtPoint(x,y) { var tags = \"\"; var e = document.elementFromPoint(x,y); while (e) { if (e.src) { tags += e.src; break; } e = e.parentNode; } return tags; }  function MyAppGetLinkHREFAtPoint(x,y) { var tags = \"\"; var e = document.elementFromPoint(x,y); while (e) { if (e.href) { tags += e.href; break; } e = e.parentNode; } return tags; }"

func viewDidLoad() {
  ...
  let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(CustomViewController.longPressRecognizerAction(_:)))
  self.webView.scrollView.addGestureRecognizer(longPressRecognizer)
  longPressRecognizer.delegate = self
  ...
}

func longPressRecognizerAction(sender: UILongPressGestureRecognizer) {
  if sender.state == UIGestureRecognizerState.Began {
    let tapPostion = sender.locationInView(self.webView)
    let tags = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetHTMLElementsAtPoint(\(tapPostion.x),\(tapPostion.y));")
    let href = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetLinkHREFAtPoint(\(tapPostion.x),\(tapPostion.y));")
    let src = self.webView.stringByEvaluatingJavaScriptFromString("MyAppGetLinkSRCAtPoint(\(tapPostion.x),\(tapPostion.y));")

    print("tags: \(tags)\nhref: \(href)\nsrc: \(src)")
    // handle the results, for example with an UIDocumentInteractionController
  }
}

// Without this function, the customLongPressRecognizer would be replaced by the original UIWebView LongPressRecognizer 
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  return true
}

И вот оно!