UIWebView – 在<img>标签上启用动作表

是只是我还是在UIWebView禁用了<img>标记的操作表? 在Safari中,例如,当您想要在本地保存图像时,请触摸并按住图像以显示操作表。 但它不能在我的自定义UIWebView中工作。 我的意思是,它仍然在为<a>标签工作,也就是说,当我触摸并按住html链接时,会显示一个操作表。 但不是<img>标签。

我试过把img { -webkit-touch-callout: inherit; } img { -webkit-touch-callout: inherit; }在CSS中,这是行不通的。 另一方面,当我双击并按住图像时,出现复制气球。

所以问题是,已经为UIWebView禁用了<img>标签的默认动作表标注? 是的,有没有办法重新启用它? 我search了很多关于如何在UIWebView中禁用它的问答,那么是不是我看到popup窗口?

提前致谢!

是的,苹果已经禁用了UIWebViews中的这个function(除其他外),并保留仅用于Safari。

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

一旦你完成了这个教程,你会想添加一些额外的东西,所以你实际上可以保存图像(本教程不包括)。 我在0.3秒后添加了一个名为@“tapAndHoldShortNotification”的额外通知,该通知调用一个只包含禁用标注代码的方法(以防止默认和自己的菜单在页面加载时popup,稍微修复一些错误)。

此外,为了检测图像,您需要扩展JSTools.js,这是我的额外function。

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

现在,您可以检测到用户点击图像,并实际find他们点击的图像url,但我们需要更改 – (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文件中声明的,以便在整个类中进行访问,以保存或打开后面的链接。

到目前为止,我们刚刚回顾了修改代码的教程代码,但是现在我们将转到本教程未涵盖的内容(在实际提到如何处理保存图像或打开链接之前停止)。

为了处理用户select,我们现在需要添加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。 因为尽pipe你可以添加更多的东西,比如一个用于instapaper的“Read Later”选项或者一个“Open In Safari”button。 (看这篇文章的长度,我看到为什么原来的教程遗漏了最终的实施细节)

编辑:(更新了更多的信息)

我被问到了我在顶部掩盖的细节,即“tapAndHoldShortNotification”,所以这是澄清的。

这是我的UIWindow子类,它添加了第二个通知来取消默认的select菜单(这是因为当我尝试教程时,它显示了两个菜单)。

 - (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支持,尽pipe这是在iPad之前编写的,因此不包括对此的支持。

在这个问题上挣扎了2到3天之后,UIWebView的“TOP-LEFT”angular落(我正在为iOS 7编程)似乎是相对“相对”计算的。

所以,为了使这个工作,当你得到的位置,在你的WebView的控制器(我会把我的代码片段下面),不要添加“滚动偏移量”

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

 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

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

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

这部分(我所做的一切覆盖默认UIActionSheet)是基于这个职位的 HEAVILY(我应该说完全)@ Freerunning的答案是完整的(我做了他在我的其他类中说的几乎所有的东西,就像我的代码基于),我发布的片段只是为了更“完全”地向您展示我的代码。

希望这可以帮助! ^^

首先感谢Freerunnering的伟大的解决scheme!

但是,您可以使用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 } 

这就是它!