iOS网络(二) WebView和WKWebview与JS的交互

1,619 阅读6分钟

一、WebView的相关简单操作

		self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"index.html" withExtension:nil];
    NSURLRequest *reqeust = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:reqeust];
#pragma mark - UIWebViewDelegate

// 加载所有请求数据,以及控制是否加载
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
  	NSLog(@"request = %@",request);
    // 路由能力 --
    NSLog(@"URL = %@",request.URL);
    NSLog(@"scheme = %@",request.URL.scheme);
  
  	NSString *scheme = request.URL.scheme;
    if ([scheme isEqualToString:@"heheorg"]) 
    {
        return NO;//可以根据要跳转的scheme设置不往下面下载,在此拦截住一个路由
    }
  	// 说明在此可以为所欲为,只要我有一套自己制定的方案
  
  	// request.URL.pathComponents 得到 oc 的方法和参数
  	/*例子:
  			NSArray *array = request.URL.pathComponents;
        // 此处伪代码没有做 非空处理 - 实际若没有非空处理则很容易出bug
        NSString *methodName = array[1];
        if ([methodName isEqualToString:@"getSum"]) 
        {
            // array[2],array[3]
            NSLog(@"%@-%@",array[2],array[3]);
        }
        
        [self performSelector:NSSelectorFromString(methodName) withObject:array afterDelay:0];
  	*/
  
  	return YES;
}
// 开始加载
- (void)webViewDidStartLoad:(UIWebView *)webView
{
    //  进度条
    NSLog(@"****************分界线****************");
    NSLog(@"开始加载");
}

// 加载完成
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    // tittle
    NSString *tittle = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    self.title = tittle;
    
    NSLog(@"****************分界线****************");
    NSLog(@"加载完成");
}

// 加载失败
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    NSLog(@"****************分界线****************");
    NSLog(@"加载失败:%@",error);
}

// oc调用js方法
- (IBAction)didClickRightItemClick:(id)sender 
{
    NSLog(@"响应按钮");
  	
    // 可用于刷新页面等功能
    // 客户端信息传入token
  
    // js 需要加密等处理 -> 返回data(有可能是异步,所以可以返回一个函数)
    // 异步 用于OC加密 ,oc加密完成后 JS才可以展示 (此处无例子)
  
    // 混合开发
    // 重定向 - H5 3次
 
   NSString *str = [self.webView stringByEvaluatingJavaScriptFromString:@"showAlert('呵呵')('哈哈')"];
    NSLog(@"%@",str);// js方法返回的数据
  /*
  js 代码:
  <script>
        function showAlert(name)
        {
            alert(name);
            return function handle(str)
            {
                alert('我是一个弹窗'+name+str);
                return name + '返回给OC';
            }
        }
    </script>
  */
}

二、JavaScriptCore

需导入JavaScriptCore.framework

1、三个关键类

  • JSContext.h

    提供了全局的上下文

  • JSValue.h

    主要是一些类型的强转(jsvalue转化成oc类型)

  • JSExport.h

    主要用在js响应oc对象,通过实现协议达到的映射来使用

2、初级使用

- (void)viewDidLoad 
{
    [super viewDidLoad];

    self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    self.webView.delegate = self;
    [self.view addSubview:self.webView];
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"index.html" withExtension:nil];;
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [self.webView loadRequest:request];
}

#pragma mark - UIWebViewDelegate
// 加载完成
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *titlt = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    self.title = titlt;
  
    //JSContext就为其提供着运行环境 H5上下文
    JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    [jsContext evaluateScript:@"var arr = ['doomfist','hehe','呵呵']"];// 定义全局变量
  	//  evaluateScript 可执行一段脚本
    
    // 清晰 --
  
  	// js代码中没有showMessage函数,但js调用了showMessage函数
  	// 类似于kvc的形式,key为showMessage,value为^{}
  	// 所以此处可以定义一个showMessage函数,一旦注册就会覆盖js代码中的同名方法
  	// 保存一段代码块
    jsContext[@"showMessage"] = ^{
        NSLog(@"来了");
        // 参数 (JS 带过来的)
        NSArray *args = [JSContext currentArguments];
        NSLog(@"args = %@",args);
    };
  
  
  	/*
  	js代码:
  	function showAlert()
  			{
            alert("hello");
            showMessage("点击了弹框按钮...",arr);
        }
  	*/
}

3、JavaScriptCore - 用于解释oc和js,是oc和js的桥梁

Js的windows中的值通过JavaScriptCore传入oc的self.windows的runloop中变成jsValue类型

- (void)setupJS
{
  	// JS-OC
    self.jsContext[@"showMessage"] = ^{
        NSLog(@"来了");
        // 参数 (JS 带过来的)
        NSArray *args = [JSContext currentArguments];
        NSLog(@"args = %@",args);
        NSLog(@"currentThis   = %@",[JSContext currentThis]);
      	// 能够监控当前Windows信息 - 没什么用
        NSLog(@"currentCallee = %@",[JSContext currentCallee]);
      	// 当前的闭包的数据 - 用处不大
        
        // OC-JS
        NSDictionary *dict = @{@"name":@"呵呵",@"age":@18};
        [[JSContext currentContext][@"ocCalljs"] callWithArguments:@[dict,@"呵呵哒"]];
      	// 调方法并传参
    };

    //JS-OC
    self.jsContext[@"showDict"] = ^{
        NSLog(@"来了");
        // 参数 (JS 带过来的)
        NSArray *args = [JSContext currentArguments];
        NSLog(@"args = %@",args);
    };
}

/* js代码
  
  			function ocCalljs(dict,str)
  			{
            var name = dict['name'];
            var age  = dict['age'];
            alert(name + age + str);
            // 传回去
            showDict(dict)
        }
  */

4、JS操作对象

// JS 操作对象
    JSObject *Object = [[JSObject alloc] init];
    self.jsContext[@"Object"] = Object;
    NSLog(@"Object == %d",[Object getSum:20 num2:40]);

/* js代码
				function openAlbumImage()
				{
            getImage();
            Object.letShowImage();
            alert(Object.getS(10,30));
        }
*/

//  ---------
<JSExport>

- (void)letShowImage;
// 协议 - 协议方法 
JSExportAs(getS, -(int)getSum:(int)num1 num2:(int)num2);

//  ---------
- (void)letShowImage
{
    NSLog(@"打开相册,上传图片");
}

- (int)getSum:(int)num1 num2:(int)num2
{
    NSLog(@"来了");
    // int (nil) - jsvalue (0)
    return num1+num2;
}
//  ---------

三、WKWebview基本用法

//加载

		WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
		// 在这里插入下面的部分↓
    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

		//  两个重要的代理 下面细讲
    self.webView.navigationDelegate = self;
    self.webView.UIDelegate = self;

    [self.view addSubview:self.webView];
    
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    [self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];
// 这里的内容插入上面↑ 如不插入则不会显示手机应该显示的正常页面
NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    [wkUController addUserScript:wkUScript];// 添加脚本
    configuration.userContentController = wkUController;
// 交互

- (IBAction)didClickRightItemAction:(id)sender 
{
   	// 调方法,传参数
    NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"登陆成功"];
  	// oc调用js 直接执行脚本 
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];

}
// 代理

#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
  	// 可以对弹窗执行重定向,改成你需要的类型
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提醒" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    
    [self presentViewController:alert animated:YES completion:nil];
}


#pragma mark - WKNavigationDelegate

// 加载完成的代理
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    self.title = webView.title;
    NSString *jsStr2 = @"var arr = [3, 'hehe', 'abc']; ";
    [self.webView evaluateJavaScript:jsStr2 completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];
}

//  拦截
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"hehe"]) 
    {
        NSString *host = [URL host];
        if ([host isEqualToString:@"jsCallOC"]) 
        {
            NSMutableDictionary *temDict = [self decoderUrl:URL];
            NSString *username = [temDict objectForKey:@"username"];
            NSString *password = [temDict objectForKey:@"password"];
            NSLog(@"%@---%@",username,password);
        }
      	else
        {
            NSLog(@"不明地址 %@",host);
        }
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}
// 另一种oc调用js
// 一定要remove,不然会循环引用
// self - webView - configuration - userContentController - self
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"messgaeOC"];
  	// addScriptMessageHandler此处注册WKScriptMessageHandler代理
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"messgaeOC"];
}

#pragma mark - WKScriptMessageHandler

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    if (message.name) 
    {
        // OC 层面的消息
    }
    NSLog(@"message == %@ --- %@",message.name,message.body);
}

/* js代码
				function messgaeHandle()
				{
            window.webkit.messageHandlers.messgaeOC.postMessage("hehe 消息");
        }
*/
附:
  wkwebview的代理的常用方法
  
//#pragma mark - WKNavigationDelegate
////请求之前,决定是否要跳转:用户点击网页上的链接,需要打开新页面时,将先调用这个方法。
//- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
////接收到相应数据后,决定是否跳转
//- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
////页面开始加载时调用
//- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
//// 主机地址被重定向时调用
//- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
//// 页面加载失败时调用
//- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
//// 当内容开始返回时调用
//- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
//// 页面加载完毕时调用
//- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
////跳转失败时调用
//- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
//// 如果需要证书验证,与使用AFN进行HTTPS证书验证是一样的
//- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler;
////9.0才能使用,web内容处理中断时会触发
//- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);

四、WebViewJavaScriptBridge

			self.wjb = [WebViewJavascriptBridge bridgeForWebView:self.webView];
    	// 如果你要在VC中实现 UIWebView的代理方法 就实现下面的代码(否则省略)
     	[self.wjb setWebViewDelegate:self];

			//  需要前端(js)适配
			[self.wjb registerHandler:@"jsCallsOC" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"currentThread == %@",[NSThread currentThread]);
        NSLog(@"data == %@ -- %@",data,responseCallback);
    }];
//  内部逻辑
+ (instancetype)bridge:(id)webView 
{
#if defined supportsWKWebView
    if ([webView isKindOfClass:[WKWebView class]]) 
    {
        return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
    }
#endif
    if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) 
    {
        WebViewJavascriptBridge* bridge = [[self alloc] init];
        [bridge _platformSpecificSetup:webView];
        return bridge;
    }
    [NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
    return nil;
}
- (IBAction)didClickLeftAction:(id)sender 
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.wjb callHandler:@"OCCallJSFunction" data:@"oc调用JS咯" responseCallback:^(id responseData) {
            NSLog(@"currentThread == %@",[NSThread currentThread]);
            NSLog(@"调用完JS后的回调:%@",responseData);
        }];
    });
}