UIwebview保存图片,长按UIWebView上的图片保存到相

2020-03-31 11:13 来源:未知

0x00 需求:长按识别UIWebView中的二维码,如下图

图片 1长按识别二维码

原文:http://www.jianshu.com/p/a388ec39e37a#

长按 UIWebView上的图片保存到相册

最近在做一个项目,里面的网络请求分为两部分,一部分是根据第三方AFNetworking封装出来的,另一个是webview自己请求显示的。需求是返回的附件,附件是一个字典。每一个字典包含一张图片的类型,请求地址,名称等信息。附件的缩略图是默认显示第一张的图片,点击缩略图,把所有的图片信息显示出来。

0x01 方案1:

给UIWebView增加一个长按手势,激活长按手势时获取当前UIWebView的截图,分析是否包含二维码。

核心代码:略

优点:流程简单,可以快速实现。

不足:无法实现保存UIWebView中图片,如果当前WebView二维码显示不全或者多个二维码,使用这种方式实现的二维码识别也会有问题;

现在H5混合原生开发的方式越来越流行,也就要用到UIWenview控件。在开发过程中,我们可能会遇到一个需求,要求我们保存网页上的图片,当用户点击图片的时候,就可以让用户选择是否下载图片。

想法一:利用JS与原生交互,JS监听图片点击事件,然后将图片的url传递给原生的APP端,原生APP将图片保存到相册

解决思路:一是根据总共返回的图片个数,在控制器内显示imageView,这个时候需要重新布局,根据SDImageView来获取所有的图片

0x02 方案2:

长按UIWebView时,获取手指单击位置的图片的URL地址。这种方案是通过获取手指点击的位置,然后获取该位置的标签的src属性,进而获取到url。

核心代码

@interface CVWebViewController ()<UIGestureRecognizerDelegate>@property (weak, nonatomic) IBOutlet UIWebView *webView;@end@implementation CVWebViewController- viewDidLoad{ [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://mp.weixin.qq.com/s?__biz=MzI2ODAzODAzMw==&mid=2650057120&idx=2&sn=c875f7d03ea3823e8dcb3dc4d0cff51d&scene=0#wechat_redirect"]]]; UILongPressGestureRecognizer *longPressed = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressed:)]; longPressed.delegate = self; [self.webView addGestureRecognizer:longPressed];}- longPressed:(UITapGestureRecognizer*)recognizer{ if (recognizer.state != UIGestureRecognizerStateBegan) { return; } CGPoint touchPoint = [recognizer locationInView:self.webView]; NSString *js = [NSString stringWithFormat:@"document.elementFromPoint.src", touchPoint.x, touchPoint.y]; NSString *imageUrl = [self.webView stringByEvaluatingJavaScriptFromString:js]; if (imageUrl.length == 0) { return; } NSLog(@"image url:%@",imageUrl); NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; UIImage *image = [UIImage imageWithData:data]; if  { //...... //save image or Extract QR code }}-gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ return YES;}

上述代码实现的核心部分就是

NSString *js = [NSString stringWithFormat:@"document.elementFromPoint.src", touchPoint.x, touchPoint.y];NSString *imageUrl = [self.webView stringByEvaluatingJavaScriptFromString:js];

第一行代码是通过js获取点击位置的标签的src属性;

第二行代码是接受向webview注入第一行的js代码后返回的src属性。

如果点击位置是图片,那么久可以通过img.src拿到图片的url地址,如果不是就返回空值。

效果

图片 2image URL注意:由于UIWebView内部是有一个ScrollView,默认情况下不支持多个手势的,因此需要实现UIGestureRecognizerDelegate中的gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:协议,以支持多个手势。

优点:通过识别img标签的url属性,既可以实现保存图片的功能,也可以实现识别图片中二维码的功能;该方案不仅仅可以获取img标签的属性,也可以根据需要获取其他标签,例如链接标签a的属性(需调整部分代码,识别tagName)。

不足:每次获取图片,都需要根据url获取,相当于从网络获取,万一图片太大或者网络不好,势必会影响用户体验,方案3中会介绍如何从缓存中获取image数据。

在系统自带的Safari浏览器已经实现了该功能,但是iOS开发中我们如果调用UIWebView加载图片,会发现无法使用Safari保存图片的功能的。这就需要我们自己去实现。

想法二:利用JS的api:Document.elementFromPoint(),实现这个功能完全可以只在APP原生端做一些代码开发。

二是,取出所有字典里面图片的地址,放到html字符串里面,把这些地址直接当成html里面图片的地址,然后直接加载,一个webview就搞定了。

0x03 方案3:

利用Runtime,动态地为UIWebView注入一段js代码,获取IMG标签的src属性,然后从UIWebView的缓存中获取image数据。

webview加载完成图片完成之后,图片数据已经缓存在webview里了,只需找到从缓存中获取这些数据的方法。

方案3使用的从缓存中获取image数据原理是:使用NSURLProtocol,webview在处理请求的过程中会调用

- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response

NSURLProtocol把webView请求返回来的data用压缩的方式的存储在cache的文件夹下, 发出请求的时候会先去读取缓存。

在github上找到了一个RNCachingURLProtocol,可以方便地从缓存中获取数据。

关于NSURLProtocol,能够让你去重新定义苹果的URL加载系统 (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个类,而且必须使用该类的子类,并且需要被注册。

核心代码

static NSString *const kTouchJavaScriptString = @"document.ontouchstart=function{ x=event.targetTouches[0].clientX; y=event.targetTouches[0].clientY; document.location="myweb:touch:start:" x ":" y;}; document.ontouchmove=function{ x=event.targetTouches[0].clientX; y=event.targetTouches[0].clientY; document.location="myweb:touch:move:" x ":" y;}; document.ontouchcancel=function{ document.location="myweb:touch:cancel";}; document.ontouchend=function{ document.location="myweb:touch:end";};";static NSString *const kImageJS = @"keyForImageJS";static NSString *const kImage = @"keyForImage";static NSString *const kImageQRString = @"keyForQR";static const NSTimeInterval KLongGestureInterval = 0.8f;...... SwizzlingMethod([self class], @selector(webViewDidStartLoad:), @selector(sl_webViewDidStartLoad:)); SwizzlingMethod([self class], @selector(webView:shouldStartLoadWithRequest:navigationType:), @selector(sl_webView:shouldStartLoadWithRequest:navigationType:)); SwizzlingMethod([self class], @selector(webViewDidFinishLoad:), @selector(sl_webViewDidFinishLoad:));......- sl_webViewDidStartLoad:(UIWebView *)webView{ //Add long press gresture for web view UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; longPress.minimumPressDuration = KLongGestureInterval; longPress.delegate = self; [self.webView addGestureRecognizer:longPress]; [self sl_webViewDidStartLoad:webView];}- sl_webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ NSString *requestString = [[request URL] absoluteString]; NSArray *components = [requestString componentsSeparatedByString:@":"]; if ([components count] > 1 && [(NSString *)[components objectAtIndex:0] isEqualToString:@"myweb"]) { if([(NSString *)[components objectAtIndex:1] isEqualToString:@"touch"]) { if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"start"]) { NSLog(@"touch start!"); float pointX = [[components objectAtIndex:3] floatValue]; float pointY = [[components objectAtIndex:4] floatValue]; NSLog(@"touch point ", pointX, pointY); NSString *js = [NSString stringWithFormat:@"document.elementFromPoint.tagName", pointX, pointY]; NSString * tagName = [self.webView stringByEvaluatingJavaScriptFromString:js]; self.imageJS = nil; if ([tagName isEqualToString:@"IMG"]) { self.imageJS = [NSString stringWithFormat:@"document.elementFromPoint.src", pointX, pointY]; } } else { if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"move"]) { NSLog(@"you are move"); } else { if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"end"]) { NSLog(@"touch end"); } } } } if (self.imageJS) { NSLog(@"touching image"); } return NO; } return [self sl_webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];}- sl_webViewDidFinishLoad:(UIWebView *)webView{ //inject js [webView stringByEvaluatingJavaScriptFromString:kTouchJavaScriptString]; [self sl_webViewDidFinishLoad:webView];}......- handleLongPress:(UILongPressGestureRecognizer *)sender{ if (sender.state != UIGestureRecognizerStateBegan) { return; } NSString *imageUrl = [self.webView stringByEvaluatingJavaScriptFromString:self.imageJS]; if  { NSData *data = nil; NSString *fileName = [RNCachingURLProtocol cachePathForURLString:imageUrl]; RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:fileName]; if  { NSLog(@"read from cache"); data = cache.data; } else{ NSLog(@"read from url"); data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; } UIImage *image = [UIImage imageWithData:data]; if  { NSLog(@"read fail"); return; } self.image = image; FSActionSheet *actionSheet = nil; if ([self isAvailableQRcodeIn:image]) { actionSheet = [[FSActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" highlightedButtonTitle:nil otherButtonTitles:@[@"Save Image", @"Extract QR code"]]; } else { actionSheet = [[FSActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" highlightedButtonTitle:nil otherButtonTitles:@[@"Save Image"]]; } [actionSheet show]; }}......- isAvailableQRcodeIn:(UIImage *)img{ if (iOS7_OR_EARLY) { return NO; } //Extract QR code by screenshot //UIImage *image = [self snapshot:self.view]; // IF image is a full qr code, CIDetector can not detect qr string, I am not sure why. UIImage *image = [self imageByInsetEdge:UIEdgeInsetsMake(-20, -20, -20, -20) withColor:[UIColor lightGrayColor] withImage:img]; CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{}]; NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]]; if (features.count >= 1) { CIQRCodeFeature *feature = [features objectAtIndex:0]; self.qrCodeString = [feature.messageString copy]; NSLog(@"QR result :%@", self.qrCodeString); return YES; } else { NSLog; return NO; }}......

效果

图片 3图片中不包含二维码图片 4图片中包含二维码

优点:充分利用了缓存,提高了用户体验。

不足:实现略复杂。

要保存网页中的图片,关键是要获取手指点击位置的图片的url地址,这就需要从js调用oc的方法。下面介绍两种方法来实现图片保存功能,但是这两种方法都只适用于图片地址用如下形式表示才可以获取:

想法二的具体操作:

图片 5

0x04 思考

如果是非IMG标签提供的图片,例如div的background image,该如何获取和保存?

WKWebView上的实现。

1

1.给UIWebView添加长按手势

- loadHTMLString:(NSString*)string baseURL:(nullableNSURL*)baseURL;

0x05 Demo地址

图片 6

2.监听手势动作,拿到坐标点x,y

但是,在不验证请求头的条件下,以上都是可以实现的,后来后台加上了请求头的验证,对于第一种实现方式,直接在网络请求的时候加上请求头即可。

0x06 参考和致谢

iOS QRcode识别及相册图片二维码读取识别

UIWebView保存图片

RNCachingURLProtocol

EndLess

FSActionSheet

如果图片是通过js动态生成的,就无法使用下面的方法获取。

3.弹出对话框,是否保存到相册

对于第二种方式,若是单次的一个webview请求,监听webview的代理方法可以实现,但是只能用一次,对于多个的图片,没法在这里添加请求头。(添加请求头之后要重新请求,会造成循环)

方法1、获取点击位置的图片的src属性

4.UIWebView注入JS:Document.elementFromPoint(x,y).src拿到img标签的src

- webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType

实现原理:

5.拿到图片的url,生成uiimage

因为程序中很多地方使用第二种方式, 基于改动最小的原则,经过分析,可以在webview,或者AFNetworking的上一层来添加请求头,这样,不管是哪种请求方式,都要经过这里和底层进行交互,也可以是url重定向,也可以解决DNS域名劫持问题,可以使用NSURLProtocol来解决。

该方法主要是获取手指点击的位置,然后获取该位置的标签的src属性,如果是img标签,那么就可以获取到url。如果是其他标签,就无法获取到url属性。

6.图片保存到相册

NSURLProtocol能够让你去重新定义苹果的URL加载系统(URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个类,而且必须使用该类的子类,并且需要被注册。

实现代码:

重点:长按手势事件不能每次都响应,所以要想长安手势准确率100%,要实现UIGestureRecognizerDelegate代理方法

不管你是通过UIWebView, NSURLConnection 或者第三方库 (AFNetworking, MKNetworkKit等),他们都是基于NSURLConnection或者 NSURLSession实现的,因此你可以通过NSURLProtocol做自定义的操作。

@interface ViewController ()@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end
@implementation ViewController

实现代码

#import "ImageSaveViewController.h"

@interface ImageSaveViewController ()

@property (strong, nonatomic) UIWebView* webview;

@end

@implementation ImageSaveViewController

- (void)viewDidLoad {

[super viewDidLoad];

self.webview = [[UIWebView alloc]init];

[self.view addSubview:self.webview];

self.webview.frame = CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-64);

NSString* url = [NSString stringWithFormat:@"];

[self.webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];

//添加长按手势

UILongPressGestureRecognizer* longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressed:)];

[self.webview addGestureRecognizer:longPressGestureRecognizer];

longPressGestureRecognizer.delegate = self;

}

#pragma mark--长按代理

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{

return YES;

}

#pragma mark-- 长按事件

- (void)longPressed:(UILongPressGestureRecognizer *)recognizer{

if (recognizer.state != UIGestureRecognizerStateBegan) {

return;

}

CGPoint touchPoint = [recognizer locationInView:self.webview];

NSString* imageURL = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y];

NSLog(@"%@",imageURL);

NSString* toSaveStr = [self.webview stringByEvaluatingJavaScriptFromString:imageURL];

NSLog(@"%@",toSaveStr);

if (toSaveStr.length == 0) {

return;

}

UIAlertController *alertVC =  [UIAlertController alertControllerWithTitle:@"尊敬的客户" message:@"您确定的要保存图片到相册吗?" preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

[self saveImageToAmblue:toSaveStr];

}];

UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"不好意思,我点错了." style:UIAlertActionStyleDefault handler:nil];

[alertVC addAction:okAction];

[alertVC addAction:cancelAction];

[self presentViewController:alertVC animated:YES completion:nil];

}

#pragma mark---保存图片

- (void)saveImageToAmblue:(NSString *)saveToURL{

NSURL* URL = [NSURL URLWithString:saveToURL];

NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

NSURLSession* session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue new]];

NSURLRequest* request = [NSURLRequest requestWithURL:URL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30.0];

NSURLSessionDownloadTask* downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {

if (error) {

return ;

}

NSData* data = [NSData dataWithContentsOfURL:location];

dispatch_async(dispatch_get_main_queue(), ^{

UIImage* iamge = [UIImage imageWithData:data];

UIImageWriteToSavedPhotosAlbum(iamge, session, @selector(imageSaveToAlbum: didFinishSavingWithError: contextInfo:), NULL);

});

}];

[downloadTask resume];

}

#pragma mark--图片保存后回调

- (void)imageSaveToAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(id)contextInfo{

NSString* message;

if (!error) {

message = @"已经成功保存到相册";

}else{

message = [error description];

}

UIAlertController* alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction* OKSection = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:nil];

[alertController addAction:OKSection];

[self presentViewController:alertController animated:YES completion:nil];

}

总结:这两种想法都有一定的局限性,想法一是太过麻烦,想法二是有的时候会获取不到图片的URL(若想准确获得URL需和前端做一些沟通)。鉴于此,具体使用哪种方法,就看你的心情了。

1.重定向网络请求

  • (void)viewDidLoad
    {
    self.webView.delegate = self;
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://image.baidu.com/wisebrowse/index?tag1=美女&tag2=全部&tag3=&pn=0&rn=10&from=index&fmpage=index&pos=magic#/home"]]];
    UILongPressGestureRecognizer * longPressed = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressed:)];
    longPressed.delegate = self;
    [self.webView addGestureRecognizer:longPressed];
    }
  • (void)longPressed:(UITapGestureRecognizer*)recognizer{
    //只在长按手势开始的时候才去获取图片的url
    if (recognizer.state != UIGestureRecognizerStateBegan) {
    return;
    }
    CGPoint touchPoint = [recognizer locationInView:self.webView];
    NSString *js = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y];
    NSString *urlToSave = [self.webView stringByEvaluatingJavaScriptFromString:js];
    if (urlToSave.length == 0) {
    return;
    }
    NSLog(@"获取到图片地址:%@",urlToSave);
    }
    //可以识别多个手势
    -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    {
    return YES;
    }
    分析:

2.忽略网络请求,使用本地缓存

上述代码的核心功能实现就是如下两行代码:
NSString *js = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y];
NSString *urlToSave = [self.webView stringByEvaluatingJavaScriptFromString:js];
第一行代码是通过js获取点击位置的标签的src属性,第二行代码是接受向webview注入第一行的js代码后返回的src属性。

3.自定义网络请求的返回结果

如果点击位置是图片,那么久可以通过img.src拿到图片的url地址,如果不是就返回空值。

4.一些全局的网络请求设置

效果展示:
方法二、遍历Dom树获取src属性

5.拦截网络请求

该方法的实现比较复杂,但是灵活性更高,不仅仅适用于保存图片。而是适用于任何从js调用oc方法的场景。

@interfaceCustomURLProtocol: NSURLProtocol@end

1、NSObject类扩展

然后在application:didFinishLaunchingWithOptions:方法中注册该CustomURLProtocol,一旦注册完毕后,它就有机会来处理所有交付给URL Loading system的网络请求。(也可以在具体的某一个控制器里面)

首先我们来给nsobject加一个类扩展,是为了可以给方法传递任意个参数,因为默认系统的performSelector方法最多只能传递两个参数

- application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {//注册protocol[NSURLProtocolregisterClass:[CustomURLProtocolclass]];returnYES;}

![Uploading 1465979028729709_957629.png . . .]

注册好了之后,现在可以开始实现NSURLProtocol的一些方法:

import "NSObject Extension.h"

@implementation NSObject (Extension)

  • (id)performSelector:(SEL)selector withObjects:(NSArray )objects
    {
    // 方法签名(对方法的描述)
    NSMethodSignature
    signature = [[self class] instanceMethodSignatureForSelector:selector];
    if (signature == nil) {
    [NSException raise:@"严重错误" format:@"(%@)方法找不到", NSStringFromSelector(selector)];
    }
    /NSInvocation : 利用一个NSInvocation对象通过调用方法签名来实现对方法的调用(方法调用者、方法名、方法参数、方法返回值)
    如果仅仅完成这步,和普通的函数调用没有区别,但是关键在于NSInvocation可以对传递进来的selector进行包装,实现可以传递无限多个参数
    普通的方法调用比如:[self performSelector: withObject: withObject:]顶多只能传递两个参数给selector
    /
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self; //调用者是自己
    invocation.selector = selector; //调用的方法是selector
    // 设置参数,可以传递无限个参数
    NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的参数个数
    paramsCount = MIN(paramsCount, objects.count); //防止函数有参数但是不传递参数时,导致崩溃
    for (NSInteger i = 0; i < paramsCount; i ) {
    id object = objects[i];
    if ([object isKindOfClass:[NSNull class]]) continue; //如果传递的参数为null,就不处理了
    [invocation setArgument:&object atIndex:i 2]; // 2是因为第一个和第二个参数默认是self和_cmd
    }
    // 调用方法
    [invocation invoke];
    // 获取返回值
    id returnValue = nil;
    if (signature.methodReturnLength) { // 有返回值类型,才去获得返回值
    [invocation getReturnValue:&returnValue];
    }
    return returnValue;
    }
    @end
    2、向webview注入js

图片 7

1465979028729709.png

在webview的代理方法webViewDidFinishLoad注入如下js代码

图片 8

1465978500848463.png

上面的方法是遍历Dom树,从中找到img标签,然后给每个img标签加上点击事件,让图片的url变

为"jscallbackoc://saveImage_*" this.src;形式,我们先不着急为什么这样做,先记住这里就好了,下面会给大家解释为什么这么做。

3、切割跳转url

在点击图片的时候,上面的注入的js代码就会起作用了,那么点击图片,就会触发绑定在该图片的时间,开始跳转,然后执行如下js代码

document.location.href="jscallbackoc://saveImage_" this.src;
此时就会生成新的图片跳转url为jscallbackoc://saveImage_
www.baidu.com

我们在webview的如下代理方法中,可以捕获到该url,然后切割处理
(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

我们捕获到跳转url后,就开始做切割处理,把他转换成oc方法,先看代码

图片 9

1465978500848463-1.png

需要注意的一点就是跳转url的格式必须如下所示:

jscallbackoc://sendMessage_number2_number3_*100$100$99

其中jscallbackoc使我们自定义的协议名字,你可以改成任意的。但是其他的部分格式必须按照上面所示

协议名格式: 协议名://
方法名: 方法签名1:方法签名2:方法签名3(方法签名用冒号连接)
参数: 参数1$参数2$参数3(多个参数用$连接)

协议名和方法名直接连接,方法名和参数用*连接

不建议把上面的冒号和$改成其他符号,比如你改成问号(?),如果原来的src的url里面包括?,那么就会出现错误。

假设被点击的图片的src地址是:www.baidu.com。那么js代码document.location.href="jscallbackoc://saveImage_" this.src;生成的图片跳转url就是jscallbackoc://saveImage_www.baidu.com

经过上面的方法切割之后,得到了方法名:saveImage和参数:www.baidu.com。

然后把方法名和参数传入最后一行的该方法,就可以执行oc代码了:

[viewController performSelector:NSSelectorFromString(methodName) withObjects:params];
此时只要在OC代码里面实现方法-(void)saveImage:(NSString *)imageURL就可以被执行了。

4、拦截跳转url

我们在webview的代理方法中,可以捕获到点击图片后的跳转url,然后做处理

图片 10

1465978422513191.png

在如上方法中我们做了判断,如果跳转过来的url包含了前缀"jscallbackoc://,就说明使我们自定义的方法,我们需要做拦截处理,转换为oc方法,但是其他跳转url就不做任务处理。

5、验证结果

我们分别点击网页上的不同图片,输出结果如下:

图片 11

1465978377543803.png

可以看到oc的方法-(void)saveImage:(NSString *)imageURL被执行了。

6、完整代码

图片 12

1465978281436926.png

图片 13

1465978353774591.png

图片 14

1465978310537472.png

总结:

方法1比较简洁,但是仅仅只能获取图片。

方法二比较繁琐,但是使用范围更广,不光可以保存图片,还可以执行任意的oc代码。

大家酌情选择。

canInitWithRequest:

这个方法主要是说明你是否打算处理对应的request,如果不打算处理,返回NO,URL Loading System会使用系统默认的行为去处理;如果打算处理,返回YES,然后你就需要处理该请求的所有东西,包括获取请求数据并返回给 URL Loading System。网络数据可以简单的通过NSURLConnection去获取,而且每个NSURLProtocol对象都有一个NSURLProtocolClient实例,可以通过该client将获取到的数据返回给URL Loading System。

这里有个需要注意的地方,想象一下,当你去加载一个URL资源的时候,URL Loading System会询问CustomURLProtocol是否能处理该请求,你返回YES,然后URL Loading System会创建一个CustomURLProtocol实例然后调用NSURLConnection去获取数据,然而这也会调用URL Loading System,而你在 canInitWithRequest:中又总是返回YES,这样URL Loading System又会创建一个CustomURLProtocol实例导致无限循环。我们应该保证每个request只被处理一次,可以通过 setProperty:forKey:inRequest:标示那些已经处理过的request,然后在 canInitWithRequest:中查询该request是否已经处理过了,如果是则返回NO。

canInitWithRequest:(NSURLRequest*)request{//只处理http和https请求NSString*scheme = [[request URL] scheme];if( ([scheme caseInsensitiveCompare:@"http"] ==NSOrderedSame|| [scheme caseInsensitiveCompare:@"https"] ==NSOrderedSame)) {//看看是否已经处理过了,防止无限循环if([NSURLProtocolpropertyForKey:URLProtocolHandledKey inRequest:request]) {returnNO; }returnYES; }returnNO;}

canonicalRequestForRequest:

通常该方法你可以简单的直接返回request,但也可以在这里修改request,比如添加header,修改host等,并返回一个新的request,这是一个抽象方法,子类必须实现。

(NSURLRequest*) canonicalRequestForRequest:(NSURLRequest*)request {NSMutableURLRequest*mutableReqeust = [request mutableCopy]; mutableReqeust = [selfredirectHostInRequset:mutableReqeust];returnmutableReqeust;} (NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request{if([request.URL host].length ==0) {returnrequest; }NSString*originUrlString = [request.URL absoluteString];NSString*originHostString = [request.URL host];NSRangehostRange = [originUrlString rangeOfString:originHostString];if(hostRange.location ==NSNotFound) {returnrequest; }//定向到bing搜索主页NSString*ip =@"cn.bing.com";// 替换域名NSString*urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];NSURL*url = [NSURLURLWithString:urlString]; request.URL = url;returnrequest;}

requestIsCacheEquivalent:toRequest:

主要判断两个request是否相同,如果相同的话可以使用缓存数据,通常只需要调用父类的实现。

requestIsCacheEquivalent:(NSURLRequest *)atoRequest:(NSURLRequest *)b{return[superrequestIsCacheEquivalent:atoRequest:b];}

-startLoading -stopLoading

这两个方法主要是开始和取消相应的request,而且需要标示那些已经处理过的request。

- startLoading{NSMutableURLRequest*mutableReqeust = [[selfrequest] mutableCopy];//标示改request已经处理过了,防止无限循环[NSURLProtocolsetProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];self.connection = [NSURLConnectionconnectionWithRequest:mutableReqeust delegate:self];}- stopLoading{ [self.connection cancel];}

NSURLConnectionDataDelegate方法

在处理网络请求的时候会调用到该代理方法,我们需要将收到的消息通过client返回给URL Loading System。

- connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response { [self.client URLProtocol:selfdidReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];}- connection:(NSURLConnection*)connection didReceiveData:data { [self.client URLProtocol:selfdidLoadData:data];}- connectionDidFinishLoading:(NSURLConnection*)connection { [self.client URLProtocolDidFinishLoading:self];}- connection:(NSURLConnection*)connection didFailWithError:error { [self.client URLProtocol:selfdidFailWithError:error];}

TAG标签:
版权声明:本文由美高梅网投平台发布于计算机网络,转载请注明出处:UIwebview保存图片,长按UIWebView上的图片保存到相