iOS WKWebView 遠端h5優先載入本地資源
阿新 • • 發佈:2019-02-12
前言: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');
}