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
參考文章和文檔:
筆者和朋友作了一個小副業,微信公衆號,替你省錢,分享還能賺點小錢; 幫忙關注,支持一下,權當請我喝咖啡,謝謝。 若是很差用,能夠取消。