WKWebview 下载文件需要通过JS注入的方式来下载。js下载的数据是base64编码的,回到给原生后,原生需要反编码后才是原始文件的数据。
具体步骤:
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKPreferences *preference = config.preferences;
preference.javaScriptEnabled = YES;
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
[wkUController addScriptMessageHandler:self name:@"onBlobData"];
config.userContentController = wkUController;
self.webView = [[WKWebview alloc] initWithFrame:self.bounds configuration:config];
关键代码 [wkUController addScriptMessageHandler:self name:@“onBlobData”]; onBlobData 就是我们定义给js调回来的接口或标识
#实现WKScriptMessageHandler 代理
重点实现函数didReceiveScriptMessage,这里需要捕获我们上一步定义的标识事件“onBlobData”,并处理对应的数据
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"onBlobData"]) {
NSString * content = message.body;
content = [content stringByReplacingOccurrencesOfString:@"data:text/xml;base64," withString:@""];// 踢出头部信息
[self saveFile:content];
}
}
content 的值如下:
data:text/xml;base64,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
其中前面的’data:text/xml;base64,'表示文件的数据:数据内容格式;编码方式。对于不同的文件有不同的内容格式,可根据具体已知要下载的文件类型存取或进一步判别这个头数据来处理。取数据时要去除这个头部信息之后再反编码,直接存文件即可。
存文件的时候我们可以存到我们指定的位置,一般情况下是弹框让用户来选定存放位置。
- (void)saveFile:(NSString *)content {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:@"yyyyMMddHHmmss"];
NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
NSString *filename = [NSString stringWithFormat:@"下载文件_%@.txt",dateString];
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setAllowsOtherFileTypes:YES];
[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"txt", nil]];
[panel setNameFieldStringValue:filename];
[panel setExtensionHidden:YES];
[panel setCanCreateDirectories:YES];
[panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton){
NSString *filePath = [[panel URL]path];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *data =[[NSData alloc] initWithBase64EncodedString:content options:0];//base64 反编码
// Generate the file path
[data writeToFile:filePath atomically:YES];//存到指定文件(直接写入)
});
}
}];
}
实现WKNavigationDelegate代理,并在decidePolicyForNavigationAction 函数中捕获要下载的bloburl,后进行下载
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"decidePolicyForNavigationAction :%@",navigationAction.request);
if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
NSString *loadUrl = navigationAction.request.URL.absoluteString;
if ([loadUrl containsString:@"blob:"] && [[loadUrl substringWithRange:NSMakeRange(0, 5)] isEqualToString:@"blob:"]) {
NSString *jsFormat = [NSString stringWithFormat:@"var xhr = new XMLHttpRequest();"
@"xhr.open('GET', '%@', true);"
@"xhr.responseType = 'blob';"
@"xhr.onload = function(e) {"
@"if (this.status == 200) {"
@"var blob = this.response;"
@"var reader = new FileReader();"
@"reader.readAsDataURL(blob);"
@"reader.onloadend = function() {"
@"window.webkit.messageHandlers.onBlobData.postMessage(reader.result);" //通过onBlobData 调回给oc代码
@"}"
@"}"
@"};"
@"xhr.send();",loadUrl];
NSString * strJSCode = [NSString stringWithFormat:@"%@", jsFormat];
[webView evaluateJavaScript:strJSCode completionHandler:^(id _Nullable data, NSError * _Nullable error) {
NSLog(@"blob:%@",strJSCode);
}];
}
decisionHandler(WKNavigationActionPolicyCancel);
NSLog(@"WKNavigationActionPolicyCancel");
} else {
decisionHandler(WKNavigationActionPolicyAllow);
NSLog(@"WKNavigationActionPolicyAllow");
}
}
重点说明xhr.open(‘GET’, ‘%@’, true); 第二个参数需要替换为捕获到的blob url,有些文章中介绍时使用‘(blob)’,就有人照抄代码,调用了没有任何结果回调,且还不知道是注入的代码不对还是请求不成功,文中通过stringWithFormat将loadurl格式化到js代码中,代码直接可用。),有些文章中介绍时使用‘(blob)’,就有人照抄代码,调用了没有任何结果回调,且还不知道是注入的代码不对还是请求不成功,文中通过stringWithFormat将loadurl格式化到js代码中,代码直接可用。