方式一:图片直接替换,这种是网页加载完成,再去替换成本地图片
UIWebView
WKWebView
都可以适用,但是它会先去加载图片,有的图加载的快再被替换的时候出现闪动,所以如果需求严格,可以选择跟方式二进行配合使用。
// MARK: UIWebViewDelegate func webViewDidFinishLoad(_ webView: UIWebView) { // 本地图片二进制 let imegData:Data = UIImagePNGRepresentation(CW_PI_KTT_420_220_BG!)! // 转换 let imageSource:String = String.init(format: "data:image/jpg;base64,%@", imegData.base64EncodedString(options: .endLineWithLineFeed)) // 获取所有的IMG标签或者IMAGE标签 进行替换 webView.stringByEvaluatingJavaScript(from: "var imgs = document.getElementsByTagName('img'); for (var i = 0; i < imgs.length; i++) { imgs[i].src = '\(imageSource)'; }") }
如果需要拦截网页里面所有图片信息,需要重写 URLProtocol
,在 NSURLSessionDataDelegate
中拦截文件Head判断 Content-Type
是否为图片格式 "image/jpeg","image/gif","image/png"...
方式二:重写 URLProtocol
拦截所有图片,这种方式可以在网页加载之前就拦截下来并替换掉图片
- 这种方式也是需要用到
方式一
中的图片替换的,方式二
只是单纯的禁用了手机APP浏览器的禁止加载指定格式的图片,图片不加载了,那还得替换成我们的占位图片,要不然就会出现一个图片加载失败的img
标签在浏览器中,所以我们还是用方式一
中的图片替换配合。 - 在允许加载图片开关处使用即可
// 图片加载开关 func CW_LOADING_PICTURE_SWITCH(_ isOn:Bool) { if isOn { URLProtocol.unregisterClass(CWURLProtocol.classForCoder()) CWURLProtocol.wk_unregister() }else{ URLProtocol.registerClass(CWURLProtocol.classForCoder()) CWURLProtocol.wk_register() } }
CWURLProtocol.swift
扩展文件,可以直接使用,也可以根据自己情况转成 OC 使用
// // CWURLProtocol.swift // XFKD // // Created by 邓泽淼 on 2018/7/11. // Copyright © 2018年 邓泽淼. All rights reserved. // import UIKit extension CWURLProtocol { /// 注册 class func wk_register() { CWURLProtocol.wk_registerScheme("http") CWURLProtocol.wk_registerScheme("https") } /// 注销 class func wk_unregister() { CWURLProtocol.wk_unregisterScheme("http") CWURLProtocol.wk_unregisterScheme("https") } } class CWURLProtocol: URLProtocol, URLSessionDataDelegate { private var session:URLSession! /// 是否拦截处理指定的请求,返回YES表示要拦截处理,返回NO表示不拦截处理 override class func canInit(with request: URLRequest) -> Bool { // 防止无限循环,因为一个请求在被拦截处理过程中,也会发起一个请求,这样又会走到这里,如果不进行处理,就会造成无限循环 if URLProtocol.property(forKey: "CWURLProtocol", in: request) != nil { return false } let url = request.url?.absoluteString ?? "" // 如果为 http || https 则拦截处理 if url.hasPrefix("http") { return true } return false } /// 如果需要对请求进行重定向,添加指定头部等操作,可以在该方法中进行 override class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } /// 开始加载 override func startLoading() { // 表示该请求已经被处理,防止无限循环 URLProtocol.setProperty(true, forKey: "CWURLProtocol", in: request as! NSMutableURLRequest) session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main) session.dataTask(with: request).resume() } /// 停止并取消请求 override func stopLoading() { session.invalidateAndCancel() session = nil } // MARK: NSURLSessionDataDelegate /// 请求结束或者是失败的时候调用 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if error != nil { client?.urlProtocol(self, didFailWithError: error!) }else{ client?.urlProtocolDidFinishLoading(self) } } /// 接收到服务器的响应 它默认会取消该请求 func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { /* NSURLSessionResponseCancel = 0, 取消 默认 NSURLSessionResponseAllow = 1, 接收 NSURLSessionResponseBecomeDownload = 2,变成下载任务 NSURLSessionResponseBecomeStream 变成流 */ let pathExtension:String = (response.url?.absoluteString as? NSString)?.pathExtension ?? "" if ["jpeg","gif","png"].contains(pathExtension) { completionHandler(.cancel) }else{ client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: URLCache.StoragePolicy.notAllowed) completionHandler(.allow) } } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { completionHandler(proposedResponse) } /// 接收到服务器返回的数据 调用多次 func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { client?.urlProtocol(self, didLoad: data) } }
CWURLProtocol
注册 跟 注销 都需要使用到另外一个扩展文件NSURLProtocol+WKWebView
NSURLProtocol+WKWebView.h
// // NSURLProtocol+WKWebView.h // XFKD // // Created by dengzemiao on 2019/1/18. // Copyright © 2019年 邓泽淼. All rights reserved. // #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface NSURLProtocol (WKWebView) + (void)wk_registerScheme:(NSString *)scheme; + (void)wk_unregisterScheme:(NSString *)scheme; @end NS_ASSUME_NONNULL_END
NSURLProtocol+WKWebView.m
// // NSURLProtocol+WKWebView.m // XFKD // // Created by dengzemiao on 2019/1/18. // Copyright © 2019年 邓泽淼. All rights reserved. // #import <Foundation/Foundation.h> #import <WebKit/WebKit.h> /// FOUNDATION_STATIC_INLINE 属于属于runtime范畴,你的.m文件需要频繁调用一个函数,可以用static inline来声明。从SDWebImage从get到的。 FOUNDATION_STATIC_INLINE Class ContextControllerClass() { static Class cls; if (!cls) { cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class]; } return cls; } FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() { return NSSelectorFromString(@"registerSchemeForCustomProtocol:"); } FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() { return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:"); } @implementation NSURLProtocol (WKWebView) + (void)wk_registerScheme:(NSString *)scheme { Class cls = ContextControllerClass(); SEL sel = RegisterSchemeSelector(); if ([(id)cls respondsToSelector:sel]) { // 放弃编辑器警告 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [(id)cls performSelector:sel withObject:scheme]; #pragma clang diagnostic pop } } + (void)wk_unregisterScheme:(NSString *)scheme { Class cls = ContextControllerClass(); SEL sel = UnregisterSchemeSelector(); if ([(id)cls respondsToSelector:sel]) { // 放弃编辑器警告 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [(id)cls performSelector:sel withObject:scheme]; #pragma clang diagnostic pop } } @end