由于我最近比较忙,先把代码贴出来,以后再增加说明。
本文以加载本地h5页面方式进行演示,若是想把它变成加载网址的方式。只需要把[self loadWebHTMLSring:@“AWWKTest”];换成[self loadWebHTMLSring:url];就可以,当然url为h5的地址;也可以在跳转页面直接调用loadWebHTMLSring方法记载地址。
测试的本地h5文件AWWKTest.html代码,就是把这个文件加入工程中就可以。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf8"> <script language="javascript"> //JS执行window.webkit.messageHandlers.onMenuShareTimeline.postMessage(<messageBody>) function onMenuShareTimelineClick() { window.webkit.messageHandlers.share.postMessage({title:'测试分享的标题',content:'测试分享的内容',url:'https://github.com/mayin1992'}); } //分享回调结果显示 function shareResult(channel_id,share_channel,share_url){ var content = channel_id+","+share_channel+","+share_url; alert(content); document.getElementById("returnValue").value = content; } </script> </head> <body> <h1>这是按钮调用</h1> <input type="btton" value="分享" onclick="onMenuShareTimelineClick()" /> <h1>回调展示区</h1> <textarea id ="returnValue" type="value" rows="5" cols="40"> </textarea> </body> </html>
AWWKWebViewController.h文件代码:
#import <UIKit/UIKit.h> @interface AWWKWebViewController : UIViewController /** 是否显示Nav */ @property (nonatomic,assign) BOOL isNavHidden; @property(nonatomic,assign) BOOL isHaveTelLoginPage; //@property (nonatomic, copy) void(^removeTelLoginPageCallBack)(void); /** 加载纯外部链接网页 @param string URL地址 */ - (void)loadWebURLSring:(NSString *)string; /** 加载本地网页 @param string 本地HTML文件名 */ - (void)loadWebHTMLSring:(NSString *)string; /** 加载外部链接POST请求(注意检查 XFWKJSPOST.html 文件是否存在 ) postData请求块 注意格式:@"\"username\":\"xxxx\",\"password\":\"xxxx\"" @param string 需要POST的URL地址 @param postData post请求块 */ - (void)POSTWebURLSring:(NSString *)string postData:(NSString *)postData; @end
AWWKWebViewController.m文件代码。这个是js调用oc的方法,oc直接调用js的方法并传参数,也可以oc处理了一些事情后或用户点击了oc的按钮后调用js的方法,自己根据自己的需要可以自己改造。
#import "AWWKWebViewController.h" #import <WebKit/WKWebView.h> #import <WebKit/WebKit.h> #import "AWJsWebEntity.h" typedef enum{ loadWebURLString = 0, loadWebHTMLString, POSTWebURLString, }wkWebLoadType; static void *WkwebBrowserContext = &WkwebBrowserContext; @interface AWWKWebViewController ()<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler,UINavigationControllerDelegate,UINavigationBarDelegate> @property (nonatomic, strong) WKWebView *wkWebView; //设置加载进度条 @property (nonatomic,strong) UIProgressView *progressView; //仅当第一次的时候加载本地JS @property(nonatomic,assign) BOOL needLoadJSPOST; //网页加载的类型 @property(nonatomic,assign) wkWebLoadType loadType; //保存的网址链接 @property (nonatomic, copy) NSString *URLString; //保存POST请求体 @property (nonatomic, copy) NSString *postData;; @property (nonatomic,strong) UIButton *rightButton; @property (nonatomic,strong) UIButton *leftBtn; @property (nonatomic,strong) NSMutableString *cookieValue; @end @implementation AWWKWebViewController - (void)viewDidLoad { [super viewDidLoad]; [self loadWebHTMLSring:@"AWWKTest"]; //加载web页面 [self webViewloadURLType]; //添加到主控制器上 [self.view addSubview:self.wkWebView]; //添加进度条 [self.view addSubview:self.progressView]; UIBarButtonItem *barButton = [[UIBarButtonItem alloc]initWithCustomView:self.rightButton]; self.navigationItem.rightBarButtonItem = barButton; UIBarButtonItem *leftBarButton = [[UIBarButtonItem alloc]initWithCustomView:self.leftBtn]; self.navigationItem.leftBarButtonItem = leftBarButton; @weakify(self); [RACObserve(self.wkWebView.scrollView, contentSize) subscribeNext:^(id x) { @strongify(self); NSLog(@"xxxxxx:%@",x); [self updateNavigationItems]; }]; } -(UIButton*)leftBtn { if (!_leftBtn) { _leftBtn =[[UIButton alloc]initWithFrame:CGRectMake(10, 20, 44, 44)]; [_leftBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal]; [_leftBtn addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside]; _leftBtn.hidden=NO; } return _leftBtn; } -(void)back { [self goToLastView]; } -(void)goToLastView { if ([self.wkWebView canGoBack]) { [self.wkWebView goBack]; } else { WKBackForwardListItem *currentItem = self.wkWebView.backForwardList.currentItem; NSString *url = [currentItem.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; if([url rangeOfString:telLoginHtml].location != NSNotFound) { [self.navigationController popViewControllerAnimated:YES]; } else { [[AWNoticeView currentNotice] showErrorNotice:@"你已经在主页,不能后退了"]; } // [self.navigationController popViewControllerAnimated:YES]; } } -(UIButton*)rightButton { if (!_rightButton) { _rightButton =[[UIButton alloc]initWithFrame:CGRectMake(FULL_WIDTH - 10 - 44, 20, 44, 44)]; [_rightButton setImage:[UIImage imageNamed:@"share"] forState:UIControlStateNormal]; [_rightButton addTarget:self action:@selector(roadLoadClicked) forControlEvents:UIControlEventTouchUpInside]; _rightButton.hidden=NO; } return _rightButton; } - (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; // if (self.removeTelLoginPageCallBack) // { // self.removeTelLoginPageCallBack(); // } if (_isNavHidden == YES) { self.navigationController.navigationBarHidden = YES; //创建一个高20的假状态栏 UIView *statusBarView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 20)]; //设置成绿色 statusBarView.backgroundColor=[UIColor whiteColor]; // 添加到 navigationBar 上 [self.view addSubview:statusBarView]; }else{ self.navigationController.navigationBarHidden = NO; } } - (void)roadLoadClicked{ [self.wkWebView reload]; } -(void)customBackItemClicked{ if (self.wkWebView.goBack) { [self.wkWebView goBack]; }else{ [self.navigationController popViewControllerAnimated:YES]; } } -(void)closeItemClicked{ [self.navigationController popViewControllerAnimated:YES]; } #pragma mark ================ 加载方式 ================ - (void)webViewloadURLType{ switch (self.loadType) { case loadWebURLString:{ //创建一个NSURLRequest 的对象 // NSURLRequest * Request_zsj = [NSURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; // //加载网页 // [self.wkWebView loadRequest:Request_zsj]; NSMutableURLRequest * Request_zsj = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; // if(self.cookieValue) // { // [Request_zsj addValue:self.cookieValue forHTTPHeaderField:@"Cookie"]; // } //加载网页 [self.wkWebView loadRequest:Request_zsj]; break; } case loadWebHTMLString:{ [self loadHostPathURL:self.URLString]; break; } case POSTWebURLString:{ // JS发送POST的Flag,为真的时候会调用JS的POST方法 self.needLoadJSPOST = YES; //POST使用预先加载本地JS方法的html实现,请确认WKJSPOST存在 [self loadHostPathURL:@"AWWKJSPOST.htm"]; break; } } } - (void)loadHostPathURL:(NSString *)url{ //获取JS所在的路径 NSString *path = [[NSBundle mainBundle] pathForResource:url ofType:@"html"]; //获得html内容 NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; //加载js [self.wkWebView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]]; } // 调用JS发送POST请求 - (void)postRequestWithJS { // 拼装成调用JavaScript的字符串 NSString *jscript = [NSString stringWithFormat:@"post('%@',{%@});", self.URLString, self.postData]; // 调用JS代码 [self.wkWebView evaluateJavaScript:jscript completionHandler:^(id object, NSError * _Nullable error) { }]; } - (void)loadWebURLSring:(NSString *)string{ self.URLString = string; self.loadType = loadWebURLString; } - (void)loadWebHTMLSring:(NSString *)string{ self.URLString = string; self.loadType = loadWebHTMLString; } - (void)POSTWebURLSring:(NSString *)string postData:(NSString *)postData{ self.URLString = string; self.postData = postData; self.loadType = POSTWebURLString; } #pragma mark ================ 自定义返回/关闭按钮 ================ -(void)updateNavigationItems{ WKBackForwardListItem *currentItem = self.wkWebView.backForwardList.currentItem; NSString *url = [currentItem.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSLog(@"current url:%@", url); self.navigationItem.leftBarButtonItem.customView.hidden = [AWGeneralFunction isHiddenWebBackButton:url]; } #pragma mark ================ WKNavigationDelegate ================ //这个是网页加载完成,导航的变化 -(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{ /* 主意:这个方法是当网页的内容全部显示(网页内的所有图片必须都正常显示)的时候调用(不是出现的时候就调用),,否则不显示,或则部分显示时这个方法就不调用。 */ // 判断是否需要加载(仅在第一次加载) if (self.needLoadJSPOST) { // 调用使用JS发送POST请求的方法 [self postRequestWithJS]; // 将Flag置为NO(后面就不需要加载了) self.needLoadJSPOST = NO; } // 获取加载网页的标题 self.title = self.wkWebView.title; // [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; [self updateNavigationItems]; // NSArray*backList = self.wkWebView.backForwardList.backList; // NSLog(@"backList:%@, backList.count = %lld", backList, backList.count); } //开始加载 -(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{ //开始加载的时候,让加载进度条显示 self.progressView.hidden = NO; } //内容返回时调用 -(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{} //服务器请求跳转的时候调用 -(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{} //服务器开始请求的时候调用 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSString *strRequest = [navigationAction.request.URL.absoluteString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSLog(@"isHaveTelLoginPage:%d, strRequest:%@",[AWSingleObject sharedInstance].isHaveTelLoginPage, strRequest); if(isEmptyString(strRequest)) { decisionHandler(WKNavigationActionPolicyCancel);//不允许跳转 return; } else if([strRequest isEqualToString:@"about:blank"]) {//主页面加载内容 decisionHandler(WKNavigationActionPolicyAllow);//允许跳转 } switch (navigationAction.navigationType) { case WKNavigationTypeLinkActivated: { break; } case WKNavigationTypeFormSubmitted: { break; } case WKNavigationTypeBackForward: { break; } case WKNavigationTypeReload: { break; } case WKNavigationTypeFormResubmitted: { break; } case WKNavigationTypeOther: { break; } default: { break; } } [self updateNavigationItems]; decisionHandler(WKNavigationActionPolicyAllow); } // 内容加载失败时候调用 -(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error{ NSLog(@"页面加载超时"); } //跳转失败的时候调用 -(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{} //进度条 -(void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{} #pragma mark ================ WKUIDelegate ================ // 获取js 里面的提示 -(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:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { completionHandler(); }]]; [self presentViewController:alert animated:YES completion:NULL]; } //KVO监听进度条 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] && object == self.wkWebView) { [self.progressView setAlpha:1.0f]; BOOL animated = self.wkWebView.estimatedProgress > self.progressView.progress; [self.progressView setProgress:self.wkWebView.estimatedProgress animated:animated]; // Once complete, fade out UIProgressView if(self.wkWebView.estimatedProgress >= 1.0f) { [UIView animateWithDuration:0.3f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^{ [self.progressView setAlpha:0.0f]; } completion:^(BOOL finished) { [self.progressView setProgress:0.0f animated:NO]; }]; } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } #pragma mark ================ WKScriptMessageHandler ================ //拦截执行网页中的JS方法 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ //服务器固定格式写法 window.webkit.messageHandlers.名字.postMessage(内容); //客户端写法 message.name isEqualToString:@"名字"] if ([message.name isEqualToString:@"share"]) { NSLog(@"%@", message.body); //oc调用js的shareResult方法并且传递参数 [self.wkWebView evaluateJavaScript:@"shareResult('dd', 'aa', '56');" completionHandler:^(id _Nullable data, NSError * _Nullable error) { }]; } } #pragma mark ================ 懒加载 ================ - (WKWebView *)wkWebView{ if (!_wkWebView) { //设置网页的配置文件 WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init]; //允许视频播放 if (@available(iOS 9.0, *)) { configuration.allowsAirPlayForMediaPlayback = YES; } else { // Fallback on earlier versions } // 允许在线播放 configuration.allowsInlineMediaPlayback = YES; // 允许可以与网页交互,选择视图 configuration.selectionGranularity = YES; // web内容处理池 configuration.processPool = [[WKProcessPool alloc] init]; //自定义配置,一般用于 js调用oc方法(OC拦截URL中的数据做自定义操作) WKUserContentController * userContentController = [[WKUserContentController alloc]init]; // 添加消息处理,注意:self指代的对象需要遵守WKScriptMessageHandler协议,结束时需要移除 [userContentController addScriptMessageHandler:self name:@"share"]; WKPreferences *preferences = [WKPreferences new]; preferences.javaScriptCanOpenWindowsAutomatically = YES; configuration.preferences = preferences; // 是否支持记忆读取 configuration.suppressesIncrementalRendering = YES; // 允许用户更改网页的设置 configuration.userContentController = userContentController; _wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration]; _wkWebView.backgroundColor = [UIColor whiteColor];//[UIColor colorWithRed:240.0/255 green:240.0/255 blue:240.0/255 alpha:1.0]; // 设置代理 _wkWebView.navigationDelegate = self; _wkWebView.UIDelegate = self; //kvo 添加进度监控 [_wkWebView addObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) options:0 context:WkwebBrowserContext]; //开启手势触摸 _wkWebView.allowsBackForwardNavigationGestures = YES; // 设置 可以前进 和 后退 //适应你设定的尺寸 [_wkWebView sizeToFit]; } return _wkWebView; } - (UIProgressView *)progressView{ if (!_progressView) { _progressView = [[UIProgressView alloc]initWithProgressViewStyle:UIProgressViewStyleDefault]; if (_isNavHidden == YES) { _progressView.frame = CGRectMake(0, 20, self.view.bounds.size.width, 3); }else{ _progressView.frame = CGRectMake(0, 64, self.view.bounds.size.width, 3); } // 设置进度条的色彩 [_progressView setTrackTintColor:[UIColor colorWithRed:240.0/255 green:240.0/255 blue:240.0/255 alpha:1.0]]; _progressView.progressTintColor = [UIColor colorWithHexString:@"0X057dff"];//[UIColor greenColor]; } return _progressView; } -(void)viewWillDisappear:(BOOL)animated{ [self.wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"share"]; [self.wkWebView setNavigationDelegate:nil]; [self.wkWebView setUIDelegate:nil]; } //注意,观察的移除 -(void)dealloc{ [self.wkWebView removeObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; } @end
iOS调用js方法:
-(void)sendJsSuccessMessageWithParams:(NSMutableDictionary *)params { if(isCommonUnitEmptyDict(params)) { return; } NSString *callBackStr = [NSString stringWithFormat:@"success(%@);", [params getJsonString]]; [self callBackWithCallBackStr:callBackStr];//注意:函数里面这个要是去除换行符的json字符串 } -(void)sendJsRechargeFailedMessage { NSString *callBackStr = [NSString stringWithFormat:@"failed();"]; [self callBackWithCallBackStr:callBackStr]; } -(void)callBackWithCallBackStr:(NSString *)callBackStr { NSLog(@"callBackStr :%@", callBackStr); if(isCommonUnitEmptyString(callBackStr)) { return; } bg_dispatch_main_async_safe(^{ //oc调用js的shareResult方法并且传递参数 [self.wkWebView evaluateJavaScript:callBackStr completionHandler:^(id _Nullable data, NSError * _Nullable error) { }]; }); }
这个是具体调用例子:
NSMutableDictionary *params = [NSMutableDictionary dictionary]; [params setValue:@"0.01" forKey:@"amount"]; [params setValue:@"cardNameValue" forKey:@"cardName"]; [self sendJsSuccessMessageWithParams:params];
由于最近项目正做根据微信端直接产生原生app,并保存微信app所有页面。踩了很多坑,特别是WKWebView的坑不是一般的深。我有其它文章解决:WKWebView和js互调方法参数传递及注意事项见文章《向js发送含有NSDictionary对象或NSArray对象的消息》。完美解决通过TZImagePickerController组件获取的多选图片对象及PHAsset数组,通过PHAsset直接获取到的图像被旋转问题及图片名称的获取见文章《通过PHAsset获取的图片上传后变大和图像被旋转90度问题完美解决方案》。WKWebView对图片地址的拦截及获取本地图片并显示问题见文章《WKWebView采用HybridNSURLProtocol协议拦截图片等资源预加载》。