Ответ 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, поэтому не включает в себя поддержку для этого.