2019-03-22

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开发-织梦者

WKWebView实现网页静态资源优先从本地加载 - JackLee18 - CSDN博客

iOS WKWebView 远端h5优先加载本地资源 - AFun_day的博客 - CSDN博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353