HTML的input标签在 type = "file"
时,即变为文件上传控件,浏览器会去监听这个标签,根据标签的另一个 accept
字段的内容去调取各个平台的相关系统资源,如图片,视频,声音等,iOS也不例外。经过这个标签,移动端的H5页面就有直接获取系统资源的能力。可是有时候咱们并不想让H5拿到原始的文件,或者是但愿可以加工一下。好比:文件的压缩,文件格式转换,文件的编辑等。git
<form>
<input type="file" accept="image/gif, image/jpeg"/>
</form>
复制代码
也许大部分状况下咱们会直接采用JS交互的方式。这种方式可定义和可控的程度都比较高,弊端也就是须要交互的地方都要跟H5协商好每一个页面去写交互代码。github
本文经过拦截的方式,笔者不认为是一种可靠的方案,由于随着iOS系统的升级极可能就变了,不利于项目的稳定,给维护带来麻烦。不过做为另一种解决问题的思路,感兴趣仍是能够看看的。浏览器
经过Debug View Hierarchy
工具查看视图树寻找点击H5标签的弹窗 第一层 bash
在拍照页面,看到了熟悉的身影,UIImagePickerController
. UIImagePickerController
类是获取选择图片和视频的用户接口,咱们能够用UIImagePickerController
选择咱们所须要的图片和视频。微信
UIImagePickerController
,这下比较能够肯定就是这个了。
先把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
熟悉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里面的信息都是什么,这里就不作过多解释了。须要的同窗能够查看 官方文档。spa
上面咱们知道,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
参考文章和文档:
笔者和朋友作了一个小副业,微信公众号,替你省钱,分享还能赚点小钱; 帮忙关注,支持一下,权当请我喝咖啡,谢谢。 若是很差用,能够取消。