iOS拦截H5的标签读取文件

HTML的input标签在 type = "file" 时,即变为文件上传控件,浏览器会去监听这个标签,根据标签的另一个 accept 字段的内容去调取各个平台的相关系统资源,如图片,视频,声音等,iOS也不例外。经过这个标签,移动端的H5页面就有直接获取系统资源的能力。可是有时候咱们并不想让H5拿到原始的文件,或者是但愿可以加工一下。好比:文件的压缩,文件格式转换,文件的编辑等。git

<form>
    <input type="file" accept="image/gif, image/jpeg"/>
</form>
复制代码

也许大部分状况下咱们会直接采用JS交互的方式。这种方式可定义和可控的程度都比较高,弊端也就是须要交互的地方都要跟H5协商好每一个页面去写交互代码。github

本文经过拦截的方式,笔者不认为是一种可靠的方案,由于随着iOS系统的升级极可能就变了,不利于项目的稳定,给维护带来麻烦。不过做为另一种解决问题的思路,感兴趣仍是能够看看的。浏览器


先以图片的获取为例

1. 寻找切入口

经过Debug View Hierarchy工具查看视图树寻找点击H5标签的弹窗 第一层 bash

第一层
显然这个ActionSheet没法决定最终是哪一张图片,这个切入点不合适,咱们再往里面看。
拍照页面

在拍照页面,看到了熟悉的身影,UIImagePickerController. UIImagePickerController类是获取选择图片和视频的用户接口,咱们能够用UIImagePickerController选择咱们所须要的图片和视频。微信

image.png
再看一下相册也是 UIImagePickerController,这下比较能够肯定就是这个了。

2.尝试hook UIImagePickerControllerDelegate

先把UIImagePickerController的delegate属性的setter方法替换成咱们本身的,以便后续修改一些代理方法。app

+ (void)hookDelegate {
    if (!isDelegateSetterHooked){
        Method originalMethod = class_getInstanceMethod([UIImagePickerController class], @selector(setDelegate:));
        Method replaceMethod = class_getInstanceMethod([UIImagePickerController class], @selector(new_setDelegate:));
        method_exchangeImplementations(originalMethod, replaceMethod);
        isDelegateSetterHooked = YES;
    }
}

/**
 替换后的delegate setter

 @param delegate delegate
 */
- (void)new_setDelegate:(id<UIImagePickerControllerDelegate>)delegate {
    
    [self new_setDelegate:delegate];//调用原来的方法实现,让UIImagePickerController的代理有值
    
    SEL swizzledSEL = @selector(swizzled_imagePickerController:didFinishPickingMediaWithInfo:);
    SEL originSEL = @selector(imagePickerController:didFinishPickingMediaWithInfo:);
    
    if ([self isKindOfClass:[UIImagePickerController class]]) {
        if (!delegate) {//代理清空时,去掉代理方法的hook
            Class class = NSClassFromString(@"WKFileUploadPanel");
            unHook_delegateMethod(class,swizzledSEL,originSEL);
            return;
        }
        hook_delegateMethod([delegate class], originSEL, [self class], swizzledSEL, swizzledSEL);
    }
}

复制代码

经过咱们本身的setter方法中的断点能够看出,此时的代理对象是WKFileUploadPanel的实例,这个类是WKWebKit的私有类,咱们没法直接使用,可使用字符串加载的方式。ide

UIImagePickerControllerDelegate的代理对象

熟悉UIImagePickerController的同窗应该知道不管是相机仍是相册,咱们最终拿到图片都是经过这个代理方法:工具

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info;
复制代码

把这个代理的实现替换掉ui

+ (void)hookDelegate {
    SEL swizzledSEL = @selector(swizzled_imagePickerController:didFinishPickingMediaWithInfo:);
    SEL originSEL = @selector(imagePickerController:didFinishPickingMediaWithInfo:);
    
    if (swizzledSEL && originSEL) {
       Class class = NSClassFromString(@"WKFileUploadPanel");
        hook_delegateMethod(class, originSEL, [UIImagePickerController class], swizzledSEL, swizzledSEL);
    }
}

/**
 替换代理方法的实现
 */
static void hook_delegateMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel, SEL noneSel)  {
    //原实例方法
    Method originalMethod = class_getInstanceMethod(originalClass, originalSel);
    //替换的实例方法
    Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);
    
    if (!originalMethod) {// 若是没有实现 delegate 方法,则手动动态添加
        Method noneMethod = class_getInstanceMethod(replacedClass, noneSel);
        class_addMethod(originalClass, originalSel, method_getImplementation(noneMethod), method_getTypeEncoding(noneMethod));
        return;
    }
    
    // 向实现 delegate 的类中添加新的方法
    class_addMethod(originalClass, replacedSel, method_getImplementation(replacedMethod), method_getTypeEncoding(replacedMethod));
    
    // 从新拿到添加被添加的 method, 由于替换的方法已经添加到原类中了, 应该交换原类中的两个方法
    Method newMethod = class_getInstanceMethod(originalClass, replacedSel);
    if(!isDelegateMethodHooked && originalMethod && newMethod) {
        method_exchangeImplementations(originalMethod, newMethod);// 实现交换
        isDelegateMethodHooked = YES;
    }
}

- (void)swizzled_imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {

}
复制代码

info数据

这是咱们就能拿到原始图像了,想怎么加工就怎么加工。 这个info里面的信息都是什么,这里就不作过多解释了。须要的同窗能够查看 官方文档spa

3. 回传信息给H5

上面咱们知道,UIImagePickerController的代理对象是WKFileUploadPanel类的实例,那么该类中一定实现了UIImagePickerControllerDelegate的代理方法。因此咱们在加工完数据以后,调用一下原始实现,把咱们的加工数据给它,从而实现替换。代码参见上面的:

- (void)swizzled_imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
复制代码

其余文件类型的拦截

<input>标签支持上传哪些媒体类型,能够查看MIME类型参考手册

这里给出几个大类,以下表格:

描述
audio/* 接受全部的声音文件。
video/* 接受全部的视频文件。
image/* 接受全部的图像文件。
MIME_type 一个有效的 MIME 类型,不带参数。请参阅 IANA MIME 类型,得到标准 MIME 类型的完整列表。

相应的HTML

<form>
    <input type="file" accept="audio/*"/>
</form>
<form>
    <input type="file" accept="video/*"/>
</form>
<form>
    <input type="file" accept="image/*"/>
</form>
<form>
    <input type="file" accept="MIME_type"/>
</form>
复制代码

笔者尝试了一下,iOS对audio/*类型的支持彷佛不是很友好,这个识别出来跟最后的MIME_type同样能选择全部文件。视频和图片这是只能选择相应类型。其它文件类型的限制和实现就留由读者们本身探索吧。

另外,在实际的需求当中可能只是须要替换H5页面的UIImagePickerControllerDelegate,也不但愿影响到其余模块。因此在demo中加了替换和恢复的代码,以及相应时机,具体请看 github


参考文章和文档:

  1. www.jianshu.com/p/626f663e9…
  2. www.w3school.com.cn/media/media…

笔者和朋友作了一个小副业,微信公众号,替你省钱,分享还能赚点小钱; 帮忙关注,支持一下,权当请我喝咖啡,谢谢。 若是很差用,能够取消。

微信公众号
相关文章
相关标签/搜索