iOS WKWebView 远端h5优先加载本地资源
前言:UIWebView调用远端h5页面,优先加载本地图片、js、css等资源,解决办法就是对请求进行拦截。
服务端代码放在本文后面
客户端需要对NSURLProtocol 的自定义类进行注册,那么所有的webview 对http请求都会被他拦截到;
首先自定义NSURLProtocol类
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import <MobileCoreServices/MobileCoreServices.h>
@interface NSURLProtocolCustom : NSURLProtocol
@end
#import "NSURLProtocolCustom.h"
static NSString* const FilteredKey = @"FilteredKey";
@implementation NSURLProtocolCustom
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSString *extension = request.URL.pathExtension;
BOOL isSource = [@[@"png", @"jpeg", @"gif", @"jpg", @"js", @"css"] indexOfObjectPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [extension compare:obj options:NSCaseInsensitiveSearch] == NSOrderedSame;
}] != NSNotFound;
return [NSURLProtocol propertyForKey:FilteredKey inRequest:request] == nil && isSource;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
- (void)startLoading
{
NSString *fileName = [super.request.URL.absoluteString componentsSeparatedByString:@"/"].lastObject;
NSLog(@"fileName is %@",fileName);
//这里是获取本地资源路径 如:png,js等
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
if (!path) {
[self sendResponseWithData:[NSData data] mimeType:nil];
return;
}
//根据路径获取MIMEType
CFStringRef pathExtension = (__bridge_retained CFStringRef)[path pathExtension];
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, NULL);
CFRelease(pathExtension);
//The UTI can be converted to a mime type:
NSString *mimeType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType);
if (type != NULL)
CFRelease(type);
//加载本地资源
NSData *data = [NSData dataWithContentsOfFile:path];
[self sendResponseWithData:data mimeType:mimeType];
}
- (void)stopLoading
{
NSLog(@"stopLoading, something went wrong!");
}
- (void)sendResponseWithData:(NSData *)data mimeType:(nullable NSString *)mimeType
{
// 这里需要用到MIMEType
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:super.request.URL
MIMEType:mimeType
expectedContentLength:-1
textEncodingName:nil];
//硬编码 开始嵌入本地资源到web中
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
@end
其次实现对类的注册
#import "CSWebView.h"
#import <WebKit/WebKit.h>
#import "NSURLProtocolCustom.h"
@interface CSWebView ()<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler>
@property (nonatomic, strong) WKWebView *wkWebView;
@end
@implementation CSWebView
- (void)viewDidLoad {
[super viewDidLoad];
//1.设置背景颜色
self.view.backgroundColor = [UIColor whiteColor];
//2.注册
[NSURLProtocol registerClass:[NSURLProtocolCustom class]];
//3.实现拦截功能,这个是核心
Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:@"http"];
[(id)cls performSelector:sel withObject:@"https"];
#pragma clang diagnostic pop
}
//4.添加WKWebView
[self addWKWebView];
}
#pragma mark - WKWebView(IOS8以上使用)
#pragma mark 添加WKWebView
- (void)addWKWebView
{
//1.创建配置项
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.selectionGranularity = WKSelectionGranularityDynamic;
//1.1 设置偏好
config.preferences = [[WKPreferences alloc] init];
config.preferences.minimumFontSize = 10;
config.preferences.javaScriptEnabled = YES;
//1.1.1 默认是不能通过JS自动打开窗口的,必须通过用户交互才能打开
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
config.processPool = [[WKProcessPool alloc] init];
//1.2 通过JS与webview内容交互配置
config.userContentController = [[WKUserContentController alloc] init];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
//2.添加WKWebView
WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIApplication sharedApplication].keyWindow.bounds.size.width, [UIApplication sharedApplication].keyWindow.bounds.size.height) configuration:config];
wkWebView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth ;
wkWebView.UIDelegate = self;
wkWebView.navigationDelegate = self;
_urlStr = [_urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:_urlStr];
[wkWebView loadRequest:[NSURLRequest requestWithURL:url]];
//[wkWebView loadRequest: [[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"app" ofType:@"html"]]]];
[self.view addSubview:wkWebView];
_wkWebView = wkWebView;
//3.注册js方法
[config.userContentController addScriptMessageHandler:self name:@"webViewApp"];
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
//接受传过来的消息从而决定app调用的方法
NSDictionary *dict = message.body;
NSString *method = [dict objectForKey:@"method"];
if ([method isEqualToString:@"hello"]) {
[self hello:[dict objectForKey:@"param1"]];
}else if ([method isEqualToString:@"Call JS"]){
[self callJS];
}else if ([method isEqualToString:@"Call JS Msg"]){
[self callJSMsg:[dict objectForKey:@"param1"]];
}
}
- (void)hello:(NSString *)param{
NSLog(@"hello");
}
- (void)callJS{
NSLog(@"callJS");
}
- (void)callJSMsg:(NSString *)msg{
NSLog(@"callJSMsg");
}
//WKNavigationDelegate
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {// 类似UIWebView的 -webViewDidStartLoad:
NSLog(@"didStartProvisionalNavigation");
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSLog(@"didCommitNavigation");
}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { // 类似UIWebView 的 -webViewDidFinishLoad:
NSLog(@"didFinishNavigation");
//[self resetControl];
if (webView.title.length > 0) {
self.title = webView.title;
}
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
// 类似 UIWebView 的- webView:didFailLoadWithError:
NSLog(@"didFailProvisionalNavigation");
}
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
decisionHandler(WKNavigationResponsePolicyAllow);
}
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
// 类似 UIWebView 的 -webView: shouldStartLoadWithRequest: navigationType:
NSLog(@"decidePolicyForNavigationAction %@",navigationAction.request);
// NSString *url = [navigationAction.request.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// NSString *url = navigationAction.request.URL.absoluteString;
decisionHandler(WKNavigationActionPolicyAllow);
}
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
NSLog(@"didReceiveServerRedirectForProvisionalNavigation");
}
//- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
// completionHandler(NSURLSessionAuthChallengePerformDefaultHandling,internal);
//}
//WKUIDelegate
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction*)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
// 接口的作用是打开新窗口委托
//[self createNewWebViewWithURL:webView.URL.absoluteString config:Web];
return _wkWebView;
}
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{ // js 里面的alert实现,如果不实现,网页的alert函数无效
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"确定"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
completionHandler();
}]];
[self presentViewController:alertController animated:YES completion:^{
}];
}
// js 里面的alert实现,如果不实现,网页的alert函数无效
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"确定"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
completionHandler(YES);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"取消"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action){
completionHandler(NO);
}]];
[self presentViewController:alertController animated:YES completion:^{}];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void(^)(NSString *))completionHandler {
completionHandler(@"Client Not handler");
}
- (void)showAlert:(NSString *)content Title:(NSString *)title{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title
message:content
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"确定"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
[self.navigationController popToRootViewControllerAnimated:YES];
}]];
[self presentViewController:alertController animated:YES completion:^{
}];
}
@end
服务端代码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>测试iOS与JS之间的互调</title>
<style type="text/css">
{
font-size: 40px;
}
</style>
</head>
<body>
<div style="rargin-top: 100px;">
<h1>Test how to use Objective-C call js</h1>
<input type="button" value="Call iOS" onclick="calliOS('call iOS')">
<input type="button" value="Call JS Alert" onclick="jsFunc()">
</div>
<div>
<input type="button" value="iOS Call With No JSON" onclick="callJS()">
<input type="button" value="iOS Call With JSON" onclick="callJSMsg('iOS Call JS')">
</div>
<div>
<span id="jsParatFuncSpan" style="color:red; font-size:50px;"></span>
</div>
</body>
<script type="text/JavaScript" src="appJs.js"></script>
</html>
本地js文件
function calliOS(Msg) {
var message = {
'method' : 'hello',
'param1' : Msg,
};
window.webkit.messageHandlers.webViewApp.postMessage(message);
}
function callJS() {
var message = {
'method' : 'Call JS',
};
window.webkit.messageHandlers.webViewApp.postMessage(message);
}
function callJSMsg(Msg) {
var message = {
'method' : 'Call JS Msg',
'param1' : Msg,
};
window.webkit.messageHandlers.webViewApp.postMessage(message);
}
function jsFunc() {
alert('Hello World');
}
iOS WKWebView 远端h5优先加载本地资源_IOS开发-织梦者
网友评论