【问题标题】:UIWebView - Enabling Action Sheets on <img> tagsUIWebView - 在 <img> 标签上启用操作表
【发布时间】:2016-12-06 18:51:39
【问题描述】:

只是我还是 &lt;img&gt; 标签上的操作表在 UIWebView 中被禁用了?例如,在 Safari 中,当您想在本地保存图像时,您可以触摸并按住图像以显示操作表。但它不适用于我的自定义 UIWebView。我的意思是,它仍然适用于&lt;a&gt; 标签,即当我触摸并按住 html 链接时,会显示一个操作表。但不适用于&lt;img&gt; 标签。

我尝试过将img { -webkit-touch-callout: inherit; } 放入css 中,但没有成功。另一方面,当我双击并按住图像时,会出现一个复制气球。

所以问题是,&lt;img&gt; 标签的默认操作表标注是否已为 UIWebView 禁用?是这样,有没有办法重新启用它?我用谷歌搜索了很多关于如何在 UIWebView 中禁用它的问答,所以只有我没有看到弹出窗口吗?

提前致谢!

【问题讨论】:

    标签: iphone ios ios4 uiwebview


    【解决方案1】:

    是的,Apple 已在 UIWebViews 中禁用此功能(以及其他功能),并仅将其保留用于 Safari。

    不过,您可以通过扩展本教程 http://www.icab.de/blog/2010/07/11/customize-the-contextual-menu-of-uiwebview/ 自行重新创建。

    完成本教程后,您需要添加一些额外的内容,以便实际保存图像(本教程未涉及)。 我在 0.3 秒后添加了一个名为 @"tapAndHoldShortNotification" 的额外通知,该通知调用了一个仅包含禁用标注代码的方法(以防止在页面仍在加载时弹出默认菜单和您自己的菜单,修复了一些错误)。

    还要检测图像,您需要扩展 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;
    }
    

    现在您可以检测到用户点击图片并实际找出他们点击的图片网址,但我们需要更改 -(void)openContextualMenuAtPoint: 方法以提供额外的选项。

    这又是我的(我试图复制 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: 方法。

    -(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 方法中设置它(无论您从哪个类处理 webview)。

        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 中的进度 hud 以改变实际下载所需的时间,但这是一个更复杂的实现)

    -(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 发布];到 dealloc 方法。

    希望这会向您展示如何将一些选项添加到苹果遗漏的 webView。 当然,尽管您可以为此添加更多内容,例如 instapaper 的“稍后阅读”选项或“在 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 个菜单(标准菜单和您的菜单)。

    您还可以通过在通知触发时发送触摸位置然后从该点显示 UIActionSheet 来轻松添加 iPad 支持,尽管这是在 iPad 之前编写的,因此不包括对此的支持。

    【讨论】:

    • tapAndHoldShortNotification 在哪里使用?
    • Ridcardo:我更新了帖子,在底部添加了更多信息。
    • 如果我没有向下滚动,这会很好用。但是,在向下滚动 WebView 然后长按这不起作用。可能它没有计算正确的接触点。请告诉我是否有人没有遇到这个问题。
    • 我在点击图像时获得了 8 个操作表。会是什么?
    【解决方案2】:

    在为这个问题苦苦挣扎 2 或 3 天之后,似乎位置是相对于 UIWebView 的“左上角”角计算的(我正在为 iOS 7 编程)。

    所以,为了使这项工作,当您获得位置时,在您的 WebView 所在的控制器上(我将在下面放置我的代码的 sn-p),不要添加“滚动偏移”

    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 - 加载Javascript

    我用这个在webview中注入我的JS代码

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

    这部分(我为覆盖默认 UIActionSheet 所做的一切)非常重要(我应该完全说)基于 this post @Freerunning 的答案是完整的(我几乎做了他在我的其他课程中所说的一切,就像我的代码所基于的帖子一样),我发布的 sn-ps 只是为了更“完整地”向您展示我的代码是如何的。

    希望这会有所帮助! ^^

    【讨论】:

      【解决方案3】:

      首先感谢 Freerunnering 提供的出色解决方案!

      但您可以使用 UILongPressGestureRecognizer 而不是自定义 LongPressRecognizer 来执行此操作。这使事情更容易实现:

      在包含webView的Viewcontroller中:

      将 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
      }
      

      就是这样!

      【讨论】:

      • 如何将 mainJavascript 附加到 web 视图?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-05
      • 1970-01-01
      • 2013-05-11
      • 1970-01-01
      • 1970-01-01
      • 2014-07-19
      相关资源
      最近更新 更多