美文网首页
2019-03-22

2019-03-22

作者: 零零321 | 来源:发表于2019-03-22 13:54 被阅读0次

    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博客

    相关文章

      网友评论

          本文标题:2019-03-22

          本文链接:https://www.haomeiwen.com/subject/zuwlvqtx.html