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