macOS/iOS WKWebview 下载文件

2023-05-16

WKWebview 下载文件需要通过JS注入的方式来下载。js下载的数据是base64编码的,回到给原生后,原生需要反编码后才是原始文件的数据。
具体步骤:

  1. 配置WKWebview的js回调句柄(标识)
  2. 创建WKWebview并添加到视图上
  3. 实现WKScriptMessageHandler的(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message,处理下载的数据
  4. 实现WKNavigationDelegate的 (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler,注入js下载代码

配置WKWebview的js回调句柄(标识)并创建

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];//存到指定文件(直接写入)
            });
        }
    }];
}

js下载代码注入

实现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代码中,代码直接可用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

macOS/iOS WKWebview 下载文件 的相关文章

  • 覆盖层不与 UITableView 一起滚动 - iOS

    我有一个 UITableView 类 它使用以下方法在转到下一个屏幕时调用加载覆盖 问题是这个加载屏幕不随列表滚动 所以如果你滚动一点并单击某些东西 加载屏幕不会显示 因为它位于顶部 如何让加载屏幕始终保持在 UITableView 的顶部
  • Firebase 身份验证问题 - 通过电子邮件地址检查用户是否存在

    我在 Firebase 上创建了一个帐户 它有效 但现在我想阻止人们使用已存在的电子邮件地址创建帐户 这是代码 DatabaseManager shared userExists with email completion weak sel
  • 减少 CoreData 的调试输出?

    我正在开发一个使用 CoreData 的 iOS macOS 项目 它工作正常 但它会向控制台输出大量调试信息 这使得控制台无法使用 因为我的打印语句隐藏在所有与 CoreData 相关的内容中 我有一个非常简单的 CoreData 设置
  • AVAssetExportSession 为零 iPhone 7 - Plus 模拟器

    AVAssetExportSession在 iPhone 6 及以下版本上运行良好 但在 iPhone 7 iPhone 7 Plus 模拟器上运行不佳 Xcode 8 0 这段代码return nil在exportSession中 当在i
  • 如何让按钮闪烁?

    我试图在扫描正确时将按钮的颜色 只是闪烁 闪烁 更改为绿色 在出现问题时将按钮的颜色更改为红色 我可以用这样的视图来做到这一点 func flashBG UIView animateWithDuration 0 7 animations s
  • UIImageJPEGRepresentation 在视网膜显示屏上提供 2x 图像

    我有这段代码 它创建一个图像 然后向其添加一些效果并缩小其大小以使其largeThumbnail UIImage originalImage UIImage imageWithData self originalImage thumbnai
  • UIView晃动动画

    我试图在按下按钮时使 UIView 摇动 我正在调整我找到的代码http www cimgf com 2008 02 27 core animation tutorial window shake effect http www cimgf
  • 将 UIToolBar 添加到所有键盘(swift)

    我正在尝试以尽可能少的重复次数将自定义 UIToolBar 添加到我的所有键盘中 我目前的做法要求我将代码添加到所有 viewDidLoads 中 并将每个文本字段的委托分配给我正在使用的 viewController 我尝试创建自己的 U
  • 如何检测用户是否第一次打开应用程序[重复]

    这个问题在这里已经有答案了 是否可以检测用户是否是第一次打开iOS应用程序 使用Objective C 我想在用户第一次打开应用程序时显示欢迎消息 但之后不再向他们显示 我正在寻找类似的东西 BOOL firstTime AppDelega
  • 如何使用 alamofire 通过基本身份验证上传图像?

    我正在尝试使用 alamofire 4 7 1 和此代码上传图像 但说实话 我怀疑我没有编写正确的代码来上传图像 func uploadDefect defectRemark String defectLocation String def
  • 在 MAC OS X 10.9 上安装 NLTK 确实很困难

    我是 Python Mac OS 新手 我正在寻找 NLTK 教科书 但我在安装它时遇到了一些问题 我一直在寻找解决方案 但不幸的是 所有解决方案似乎都不适合我 或者我误解了如何使用它们 我遇到的基本问题是 尽管按照说明进行操作 NLTK
  • 使用数组中的字符串淡入/淡出标签

    func setOverlayTitle self overlayLogo text Welcome var hello String Bon Jour GUTEN nMORGEN BONJOUR HOLA BUENOS D AS BUON
  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • 使用输入类型 = 文件捕获照片时移动 safari 崩溃

    我正在使用输入类型文件在 iOS 中启动相机 我正在使用以下代码行
  • 有没有办法在 onclick 触发时禁用 iPad/iPhone 上的闪烁/闪烁?

    所以我有一个有 onclick 事件的区域 在常规浏览器上单击时 它不会显示任何视觉变化 但在 iPad iPhone 上单击时 它会闪烁 闪烁 有什么办法可以阻止它在 iPad iPhone 上执行此操作吗 这是一个与我正在做的类似的示例
  • 诊断和仪器均缺少“僵尸”选项

    运行 Xcode 4 0 2 Zombie 选项丢失 其他 SO 帖子建议找到它的两个地方 Product gt Run looks like this Product gt Profile looks like this 奇怪的是 我之前
  • ios8 键盘高度有所不同

    我使用下面的代码来获取键盘高度 该高度在带有 ios8 的 iPhone 5s 设备中与带有 ios7 的 IPhone4s 设备中有所不同 因此 当我在带有 ios8 的 iPhone5s 中点击它时 我的文本字段移动得非常高 而相同的代
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • 致命错误:在 Swift 中解包可选值时意外发现 nil

    所以我试图获取 Swift 中输入字段的文本 这就是我得到的 class ViewController UIViewController IBOutlet var passwordField UITextField IBOutlet var
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两

随机推荐

  • yum配置代理proxy

    yum代理proxy xff1a Linux CentOS设置全局代理 xff08 http xff09 说明 xff1a 为什么说是http代理 xff0c 其实这个还不能说是全称走代理 xff0c 罪名写的区别就是ICMP协议这个设置就
  • 002 如何将date命令显示的中文时间改为显示英文时间?

    本来执行date命令显示中文时间 date 2017年 11月 25日 星期六 15 39 49 CST lancer 64 ubuntu locale LANG 61 en US UTF 8 LANGUAGE 61 LC CTYPE 61
  • github pages 用法详解

    github pages 基础用法 1 URL 规则 假设你的 github 帐号为 mygithub xff0c 需要发布的仓库名为 myrepo xff0c 那么 pages 的 URL 为 xff1a https mygithub g
  • 文章标题

    刷leetcode的时候总是喜欢把别人家的C 43 43 和JAVA翻译成Python表达 xff0c 然后看自己代码能够短多少 所谓Pythonic啊 xff0c 真是个好东西 xff0c 但是未必能够让算法时间复杂度减低 xff0c 就
  • ld: error: undefined symbol: did you mean: extern “C“

    碰到个问题 xff0c 先看报错 xff1a FAILURE Build completed with 2 failures 1 Task failed with an exception What went wrong Execution
  • git 出现fatal: not a git repository (or any of the parent directories): .git 问题解决

    今天在一个新的磁盘上 xff0c 新建了一个目录 xff0c 打算把gitlab上的一个项目临时本地改一下 xff0c 结果本地执行git clone项目 xff0c 可以成功 xff0c 能把项目从gitlab上把代码同步下来 xff0c
  • Ring Buffer (circular Buffer)环形缓冲区简介

    关于环形缓冲区的知识 xff0c 请看这里 http en wikipedia org wiki Circular buffer 上面这个网址已经介绍得非常详细了 下面这个网址有 RingBuffer的C代码实现 xff0c 其实是一个C的
  • iOS——MRC和ARC实现原理

    Objective C提供了两种内存管理机制MRC xff08 Mannul Reference Counting xff09 和ARC xff08 Automatic Reference Counting xff09 xff0c 为Obj
  • [Python实战项目] - xpath 爬虫实战,获取纵横小说网连载小说最新章节(一)

    本教程可以一键获取连载小说最新章节 xff0c 只需要运行一下代码 xff0c 直接打开浏览器看小说未看的最新章节 开发环境 Python版本 xff1a python3 6Python第三方库 xff1a requests lxml we
  • 计算机组成原理选择题题库

    目录 第一套 第二套 第三套 第四套 第五套 第六套 第七套 第八套 第九套 第十套 第一套 一 选择题 xff08 每小题1分 xff0c 共15分 xff09 1 从器件角度看 xff0c 计算机经历了五代变化 但从系统结构看 xff0
  • 【面试必备】面向Android开发者的复习指南!最全的BAT大厂面试题整理

    近日一好友去阿里面试 xff0c 面试失败了 xff0c 分享了一个他最不擅长的算法面试题 题目是这样的 题目 xff1a 给定一个二叉搜索树 BST xff0c 找到树中第 K 小的节点 出题人 xff1a 阿里巴巴出题专家 xff1a
  • 文件选择器DocumentUI显示apk文件图标

    1 定位代码与调试 Hierarchy Viewer分析界面工具 xff0c uiautomatorviewer自动化查看器 xff0c 定位组件 添加调试信息 xff0c 调试定位代码 2 搜索获取apk文件图标 lt uses perm
  • Android adb 启动APP

    目录 启动命令一 常规命令 43 包名 activity二 常规命令 43 包名命令关闭App 获取包名和activity的路径代码获取1 命令获取 需要app运行在前台 xff0c 停留在启动界面 2命令获取 先执行命令 xff0c 再点
  • Android日志[基础篇]Android Log日志输出

    Android日志 基础篇 二 Android Studio修改LogCat日志的颜色 android util Log输出日志的常用方法如下 xff1a Log v String tag String msg Log d String t
  • Android WebView https白屏、Http和Https混合问题、证书配置和使用

    目录 前言启用https后白屏 xff08 证书错误 xff09 修改处理WebView中Http和Https混合问题处理办法Webview的几种内容加载模式 证书配置或处理https请求的证书okhttp进行请求 xff1a HttpsU
  • Java错误:找不到或无法加载主类

    目录 前言javac xxx java 编译需要相对物理路径java xxx 执行需要虚拟路径总结 前言 一般情况下 xff0c 我们都使用工具进行代码的编辑和调试 xff0c 例如eclipse Manven Android Studio
  • Edge 修改字符编码(详细图文)

    Microsoft Edge 版本 97 0 1072 62 官方内部版本 64 位 前言 如下图 xff0c 在访问页面时出现乱码 xff0c 而且一直返回的内容编码是UTF 8 xff0c 但Edge没找快捷的编码方式选择 方法一 In
  • Charles抓取HTTPS Windows Android iOS 图文详细

    文章目录 背景操作原理windows 安装CharlesCharles配置第一步 xff1a 配置HTTP代理 xff0c 这步与抓取HTTP请求是一样第二步 xff1a 配置SSL代理第三步 xff1a 为手机配置代理iPhone 代理配
  • Linux less 命令使用介绍

    文章目录 1 xff0e 命令格式2 xff0e 命令功能3 xff0e 命令参数4 xff0e 按键操作5 xff0e 示例1 查看文件内容2 ps查看进程信息并通过less分页显示3 查看命令历史使用记录并通过less分页显示5 浏览多
  • macOS/iOS WKWebview 下载文件

    WKWebview 下载文件需要通过JS注入的方式来下载 js下载的数据是base64编码的 xff0c 回到给原生后 xff0c 原生需要反编码后才是原始文件的数据 具体步骤 xff1a 配置WKWebview的js回调句柄 xff08