一、UIWebView UIWebView基本用法 UIWebView可以加载本地或远程HTML页面。使用UIWebView的loadRequest:
即可完成加载工作,十分简单。但一般我们加载本地HTML页面时,会使用UIWebView的loadHTMLString:
方法,这么做有两点好处:一是安全,二是避免中文乱码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #import "ViewController.h" @interface ViewController ()<UIWebViewDelegate > { UIWebView *myWebView; } @end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; self .view .backgroundColor = [UIColor whiteColor]; myWebView = [[UIWebView alloc] initWithFrame:CGRectMake (0 , 0 , [UIScreen mainScreen].bounds .size .width , [UIScreen mainScreen].bounds .size .height )]; myWebView.delegate = self ; [self .view addSubview:myWebView]; [self loadRemoteHtml]; } - (void )loadLocalHtml { NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"hello" ofType:@"html" ]; NSString *htmlString = [[NSString alloc] initWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil ]; [myWebView loadHTMLString: htmlString baseURL: nil ]; } - (void )loadRemoteHtml { [myWebView loadRequest:[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.baidu.com" ]]]; } #pragma mark --webViewDelegate -(BOOL )webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType )navigationType { NSLog (@"网页加载之前会调用webView:shouldStartLoadWithRequest:navigationType:方法" ); return YES ; } -(void )webViewDidStartLoad:(UIWebView *)webView { NSLog (@"开始加载网页时调用webViewDidStartLoad:方法" ); } -(void )webViewDidFinishLoad:(UIWebView *)webView { NSLog (@"网页加载完成调用webViewDidFinishLoad:方法" ); } -(void )webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { NSLog (@"网页加载失败会调用webView:didFailLoadWithError:方法" ); } @end
hello.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE HTML> <html > <body > <H2 id = "myName "> Hi,I`m Liyanliang</H2 > <canvas id ="myCanvas" width ="200" height ="100" style ="border:1px solid #c3c3c3;" > Your browser does not support the canvas element. </canvas > <br /> <a href ="https://www.tmall.com" > 前往天猫商城(OC中会强行重定向到京东)</a > <br /> <input type ="button" value ="JS调用iOS方法-UIWebView处理" onclick ="callOCMethord()" /> <input type ="button" value ="JS调用iOS方法-WKWebView处理" onclick ="callOCMethord2()" /> <script src ="myjs.js" > </script > </body > </html >
myjs.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function drawLinearGradient () { var c=document.getElementById("myCanvas" ) ; var cxt=c.getContext("2d" ) ; var grd=cxt.createLinearGradient(0 ,0 ,175 ,50 ) ; grd.addColorStop(0 ,"#FF0000" ) ; grd.addColorStop(1 ,"#00FF00" ) ; cxt.fillStyle=grd; cxt.fillRect(0 ,0 ,175 ,50 ) ; } function sendCommand (cmd,param) { var url="jsCalledOCCommand:" +cmd+":" +param; document.location = url; } function callOCMethord () { sendCommand("Alert" ,"这是JS传递的内容" ) ; } function callOCMethord2 () { window.webkit.messageHandlers.TestJSMessage.postMessage("这是JS传递的内容" ) ; }
如果你使用的iOS版本在9.0及9.0之上,运行上面代码,控制台会输出警告:
1 App Transport Security has blocked a cleartext HTTP (http ://) resource load since it is insecure. Temporary exceptions can be configured via your app’s Info.plist file .
解决办法请参考我的另外一篇文章:iOS网络开发 中的) 2.1 章节。
解决UIWebView Delegate方法多次调用问题 我们看一下UIWebView的几个Delegate方法。如果您运行上述示例程序,会发现这几个Delegate方法会执行多次,这是因为网页内有异步请求或者重定向时,就会多次调用这几个Delegate方法,解决方法是使用webView.isLoading属性(详情请参考stackoverflow ):
1 2 3 4 5 6 7 -(void )webViewDidFinishLoad:(UIWebView *)webView { if (webView.isLoading ) { return ; } NSLog (@"网页加载完成调用webViewDidFinishLoad:方法" ); }
UIWebView调用JS方法 使用UIWebView的stringByEvaluatingJavaScriptFromString:
即可执行JS方法。需要注意的是,对于本地JS文件,我们也需要先执行stringByEvaluatingJavaScriptFromString:
,之后才能调用本地JS文件中的具体JS方法。
即便在您的HTML文件中引用了本地js文件,如:<script src="myjs.js"></script>
,如果要在OC中调用该JS文件中的相关方案,依然需要使用stringByEvaluatingJavaScriptFromString:
把整个JS文件包含进来,才能调用本地JS文件中的具体JS方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 -(void )webViewDidFinishLoad: (UIWebView *)webView { NSLog(@"网页加载完成调用webViewDidFinishLoad:方法" ); if (webView.isLoading) { return ; } NSString *jsPath = [[NSBundle mainBundle] pathForResource: @"myjs" ofType: @"js" ]; NSString *jsString = [[NSString alloc] initWithContentsOfFile: jsPath encoding: NSUTF8StringEncoding error: nil]; [myWebView stringByEvaluatingJavaScriptFromString: jsString]; [myWebView stringByEvaluatingJavaScriptFromString: @"drawLinearGradient();" ]; }
UIWebView管理页面跳转 我们在开发中会遇到这样的问题:比如我是京东员工(不要猜测,我本人真不是京东员工),上级告诉我,在我们的APP中,任何跳转到天猫的行为都要被阻止,并强行跳转到京东首页。你会怎么做?
我们可以在webView:shouldStartLoadWithRequest:navigationType:
这个代理方法中管理页面跳转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 -(BOOL )webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType )navigationType { if (!webView.isLoading ) { NSString *willToURL = [[request URL] absoluteString]; if ([willToURL containsString:@"tmall.com" ]) { [myWebView loadRequest:[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.jd.com" ]]]; return NO ; } return YES ; } else { return NO ; } }
UIWebView实现JS调用OC方法 同样也是在webView:shouldStartLoadWithRequest:navigationType:
这个代理方法中实现。其思路是:
1、js中定义一个特殊的url,并控制document.location跳转到该url。
1 2 3 4 function sendCommand (cmd,param ) { var url="jsCalledOCCommand:" +cmd+":" +param; document .location = url; }
2、OC代码中,在webView:shouldStartLoadWithRequest:navigationType:
这个代理方法中,监控页面跳转,如果发现是我们规定好的那个特殊的url,则调用相应OC方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 -(BOOL )webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType )navigationType { if (!webView.isLoading ) { NSString *willToURL = [[request URL] absoluteString]; NSArray *components = [willToURL componentsSeparatedByString:@":" ]; if ([components count] >= 3 && [[components objectAtIndex:0 ] isEqualToString:@"jscalledoccommand" ] && [[components objectAtIndex:1 ] isEqualToString:@"Alert" ]) { NSString *message = [[components objectAtIndex:2 ] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding ]; [self showAlertMessageWithTitle:@"来自JS的呼唤" message:message]; return NO ; } return YES ; } else { return NO ; } } - (void )showAlertMessageWithTitle:(NSString *)title message:(NSString *)message { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert ]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDestructive handler:nil ]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"用Safari打开百度" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.baidu.com" ]]; }]; [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){ textField.placeholder = @"登录" ; }]; [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.placeholder = @"密码" ; textField.secureTextEntry = YES ; }]; [alertController addAction:cancelAction]; [alertController addAction:okAction]; [self presentViewController:alertController animated:YES completion:nil ]; }
使用Safari打开URL 上面代码中我们也看到了,我们想要使用默认浏览器来打开web,只用调用UIApplication单例的openURL方法即可。
1 [[UIApplication sharedApplication] openURL :[NSURL URLWithString:@"http://www.baidu.com" ]] ;
UIWebView 加载其它类型的文件
UIWebView打开本地pdf、word文件依靠的并不是UIWebView自身解析,而是依靠MIME Type识别文件类型并调用对应应用打开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // //加载 PDF 内容 // NSURL *pdfFileURL = [[NSBundle mainBundle ] URLForResource :@"OReilly.Learning.AngularJS.2015.3" withExtension :@"pdf" ]; // NSData *pdfFileData = [NSData dataWithContentsOfURL :pdfFileURL ]; // [myWebView loadData :pdfFileData MIMEType :@"application/pdf" textEncodingName :@"utf-8" baseURL :pdfFileURL ]; //加载 PDF 内容 , 用loadRequest 方法更简单 NSURL *pdfFileURL = [[NSBundle mainBundle ] URLForResource :@"OReilly.Learning.AngularJS.2015.3" withExtension :@"pdf" ]; [myWebView loadRequest :[NSURLRequest requestWithURL :pdfFileURL ]]; //加载 xlsx 内容--不行 // NSURL *fileURL = [[NSBundle mainBundle ] URLForResource :@"xlsx test" withExtension :@"xls" ]; // NSData *fileData = [NSData dataWithContentsOfURL :fileURL ]; // [myWebView loadData :fileData MIMEType :@"application/vnd.ms-excel" textEncodingName :@"utf-8" baseURL :fileURL ]; //加载 txt 内容 // NSURL *fileURL = [[NSBundle mainBundle ] URLForResource :@"txtTest" withExtension :@"txt" ]; // NSData *fileData = [NSData dataWithContentsOfURL :fileURL ]; // [myWebView loadData :fileData MIMEType :@"text/plain" textEncodingName :@"utf-8" baseURL :fileURL ];
避免 UIWebView 内存泄露 UIWebView Class Reference 中有这么一段:
IMPORTANT
Before releasing an instance of UIWebView for which you have set a delegate, you must first set its delegate property to nil. This can be done, for example, in your dealloc method.
所以,我们需要这么做来防止内存泄露:
1 2 3 4 - (void )dealloc { myWebView.delegate = nil ; myWebView = nil ; }
二、WKWebView WKWebView新特性 ios8之后,推荐用WKWebView来替代UIWebView。WKWebView相对于UIWebView内存消耗相对减少,所提供的接口也丰富了许多。大家可以先移步WKWebView - NSHipster 看看介绍。
曾经的UIWebView,在iOS上,使用的是UIKit.framework的UIWebView;在OS X上,使用的是WebKit.framework的WebView。
iOS 8 的WKWebView:
和OS X使用统一的framework,意味着可移植性提高了。除此之外
更好的性能,如对网页滑动的响应。
更好的JavaScript引擎。
内置前进后退手势。
更有效的JS和App的交互。
最重头的,新的多进程模型。
WebKit API 1、基本属性 网页加载进度(estimatedProgress加载进度条,在IOS8之前我们是通过一个假的进度条来实现)、网页标题,这些网页的最最基本的属性,终于齐了、backForwardList表示historyList、WKWebViewConfiguration *configuration;初始化webview的配置。
2、前进后退手势 在UIWebView这个功能十分复杂,而WKWebView内置
3、WKPreferences 对应WebView的WebViewPreference,相比UIWebView,增加了禁用JavaScript功能,但没有无图模式,差评。
4、WKUserContentController JS通讯相关,App注入JS时,可以指定时机(加载开始或加载结束)和范围(MainFrame或所有Frame);另外内置JS Bridge。
5、WKProcessPool 和多进程模型相关,目前功能未知。
6、WKBackForwardList 前进后退列表。
7、WKNavigationDelegate 类似于WebView里的WebFrameLoadDelegate,功能稍稍阉割了下,但基本能用。
8、WKUIDelegate UI相关的回调。如新窗口打开、页面alert弹框等的处理回调。
9、增加初始化方法 1 - (instancetype) initWithFrame:(CGRect ) frame configuration:(WKWebViewConfiguration *) configuration
10、跳到历史的某个页面 1 - (WKNavigation *)goToBackForwardListItem :(WKBackForwardListItem *)item ;
11、相同的属性和方法 goBack、goForward、canGoBack、canGoForward、stopLoading、loadRequest、scrollView
12、被删去的属性和方法 在跟js交互时,我们使用这个API,目前WKWebView完档没有给出实现类似功能的API
1 - (NSString *)stringByEvaluatingJavaScriptFromString :(NSString *)script ;
无法设置缓存:在UIWebView,使用NSURLCache缓存,通过setSharedURLCache可以设置成我们自己的缓存,但WKWebView不支持NSURLCache
13、delegate方法的不同 UIWebView支持的代理是UIWebViewDelegate,WKWebView支持的代理是WKNavigationDelegate和WKUIDelegate
WKNavigationDelegate主要实现了涉及到导航跳转方面的回调方法
WKUIDelegate主要实现了涉及到界面显示的回调方法:如WKWebView的改变和js相关内容。
具体来说WKNavigationDelegate除了有开始加载、加载成功、加载失败的API外,还具有额外的三个代理方法:
1 - (void) webView:(WKWebView *) webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *) navigation
这个代理是服务器redirect时调用
1 - (void) webView:(WKWebView *) webView decidePolicyForNavigationResponse:(WKNavigationResponse *) navigationResponse decisionHandler:(void (^) (WKNavigationResponsePolicy) ) decisionHandler
这个代理方法表示当客户端收到服务器的响应头,根据response相关信息,可以决定这次跳转是否可以继续进行。
1 - (void) webView:(WKWebView *) webView decidePolicyForNavigationAction:(WKNavigationAction *) navigationAction decisionHandler:(void (^) (WKNavigationActionPolicy) ) decisionHandler
根据webView、navigationAction相关信息决定这次跳转是否可以继续进行,这些信息包含HTTP发送请求,如头部包含User-Agent,Accept。
更多细节,可以查看WebKit Objective-C Framework Reference
WKWebView加载远程HTML页面及KVO检测加载进度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #import "ViewController2.h" #import <WebKit/WebKit.h> @interface ViewController2 ()<WKNavigationDelegate >@property (nonatomic ,strong )WKWebView *webView;@end @implementation ViewController2 - (void )viewDidLoad { [super viewDidLoad]; self .webView = [[WKWebView alloc] initWithFrame:self .view .bounds ]; [self .view addSubview:self .webView ]; self .webView .navigationDelegate = self ; [self .webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.jd.com" ]]]; self .webView .allowsBackForwardNavigationGestures = YES ; } - (void )viewWillAppear:(BOOL )animated { [super viewWillAppear:animated]; [self .webView addObserver:self forKeyPath:@"loading" options:NSKeyValueObservingOptionNew context:nil ]; [self .webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil ]; [self .webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil ]; } - (void )viewDidDisappear:(BOOL )animated { [super viewDidDisappear:YES ]; [self .webView removeObserver:self forKeyPath:@"loading" context:nil ]; [self .webView removeObserver:self forKeyPath:@"title" context:nil ]; [self .webView removeObserver:self forKeyPath:@"estimatedProgress" context:nil ]; } - (void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary <NSString *,id > *)change context:(void *)context { if ([keyPath isEqualToString:@"loading" ]) { NSLog (@"loading:%d" ,self .webView .loading ); }else if ([keyPath isEqualToString:@"title" ]) { NSLog (@"title:%@" ,self .webView .title ); }else if ([keyPath isEqualToString:@"estimatedProgress" ]) { NSLog (@"estimatedProgress:%f" ,self .webView .estimatedProgress ); } } #pragma mark - WKNavigationDelegate - (void )webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSLog (@"1?、decidePolicyForNavigationAction-类似UIWebView的webView:shouldStartLoadWithRequest:navigationType:,根据webView、navigationAction相关信息决定这次跳转是否可以继续进行,这些信息包含HTTP发送请求,如头部包含User-Agent,Accept" ); decisionHandler(WKNavigationActionPolicyAllow); } - (void )webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { NSLog (@"2、didStartProvisionalNavigation-类似UIWebView的webViewDidStartLoad:,页面开始加载时调用" ); [UIApplication sharedApplication].networkActivityIndicatorVisible = YES ; } - (void )webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation { NSLog (@"3?、didReceiveServerRedirectForProvisionalNavigation-服务器redirect时调用" ); } - (void )webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { NSLog (@"4、decidePolicyForNavigationResponse-当客户端收到服务器的响应头,根据response相关信息,可以决定这次跳转是否可以继续进行" ); decisionHandler(WKNavigationResponsePolicyAllow); } - (void )webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { NSLog (@"5、didCommitNavigation-内容开始返回时调用" ); } - (void )webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { NSLog (@"6、didFinishNavigation-类似UIWebView的webViewDidFinishLoad:,页面加载完成后调用" ); } - (void )webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { NSLog (@"%s-类似UIWebView的webView:didFailLoadWithError:,页面加载失败时调用" ,__FUNCTION__); } @end
WKWebView 与 Javascript 交互 先贴上源码(html和js源码见上文UIWebView部分)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 #import "ViewController3.h" #import <WebKit/WebKit.h> @interface ViewController3 ()<WKNavigationDelegate ,WKUIDelegate ,WKScriptMessageHandler >@property (nonatomic ,strong ) WKWebView *webView;@property (nonatomic ,strong ) WKWebViewConfiguration *configuretion;@end @implementation ViewController3 - (void )viewDidLoad { [super viewDidLoad]; WKPreferences *preferences = [[WKPreferences alloc] init]; preferences.minimumFontSize = 10 ; preferences.javaScriptEnabled = true ; preferences.javaScriptCanOpenWindowsAutomatically = false ; WKUserContentController *userContentController = [[WKUserContentController alloc] init]; self .configuretion = [[WKWebViewConfiguration alloc] init]; self .configuretion .preferences = preferences; self .configuretion .userContentController = userContentController; self .webView = [[WKWebView alloc] initWithFrame:self .view .bounds configuration:self .configuretion ]; [self .view addSubview:self .webView ]; self .webView .navigationDelegate = self ; self .webView .UIDelegate = self ; self .webView .allowsBackForwardNavigationGestures = YES ; [self loadLocalHtml]; [self testAddScriptMessageName]; [self testAddUserScript]; } * 加载本地html文件 */ - (void )loadLocalHtml { NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"hello" ofType:@"html" ]; NSURL *htmlURL = [[NSURL alloc] initFileURLWithPath:htmlPath]; NSString *htmlString = [[NSString alloc] initWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil ]; [self .webView loadRequest:[[NSURLRequest alloc] initWithURL:htmlURL]]; [self .webView loadHTMLString: htmlString baseURL: nil ]; } * 添加一个名称,来匹配js的回调。 * js端需要固定用window.webkit.messageHandlers.<name>.postMessage();来想OC发送消息 * OC端用WKScriptMessageHandler protocal来接收消息 */ - (void )testAddScriptMessageName { [self .configuretion .userContentController addScriptMessageHandler:self name:@"TestJSMessage" ]; } *使用用户脚本来注入 JavaScript */ - (void )testAddUserScript { WKUserScript *userScript2 = [[WKUserScript alloc] initWithSource:@"function changeMyName() { document.getElementById('myName').innerHTML='LYL\\'s Blog'; }" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES ]; WKUserScript *userScript2_1 = [[WKUserScript alloc] initWithSource:@" changeMyName();" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES ]; [self .configuretion .userContentController addUserScript:userScript2]; [self .configuretion .userContentController addUserScript:userScript2_1]; } #pragma mark - WKNavigationDelegate - (void )webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"myjs" ofType:@"js" ]; NSString *jsString = [[NSString alloc] initWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil ]; [self .webView evaluateJavaScript:jsString completionHandler:^(id data, NSError * error) { }]; [self .webView evaluateJavaScript:@"drawLinearGradient();" completionHandler:^(id data, NSError * error) { }]; } #pragma mark - WKScriptMessageHandler - (void )userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { NSLog (@"message name is %@" ,message.name ); NSLog (@"message body is %@" ,message.body ); if ([message.name isEqualToString:@"TestJSMessage" ] ){ [self .webView evaluateJavaScript:@"alert('这是Alert');" completionHandler:^(id data, NSError * error) { }]; [self .webView evaluateJavaScript:@"confirm('这是Confirm');" completionHandler:^(id data, NSError * error) { }]; [self .webView evaluateJavaScript:@"prompt('这是Prompt','lyl');" completionHandler:^(id data, NSError * error) { }]; } } #pragma mark - WKUIDelegate -(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { if (!navigationAction.targetFrame .isMainFrame ) { [webView loadRequest:navigationAction.request ]; } return nil ; } - (void )webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void ))completionHandler { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:webView.URL .host message:message preferredStyle:UIAlertControllerStyleAlert ]; [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString (@"【关闭】" , nil ) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { completionHandler(); }]]; [self presentViewController:alertController animated:YES completion:nil ]; } - (void )webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL ))completionHandler { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:webView.URL .host message:message preferredStyle:UIAlertControllerStyleAlert ]; [alertController addAction:[UIAlertAction actionWithTitle:@"【确定】" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { completionHandler(YES ); }]]; [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString (@"【取消】" , nil ) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { completionHandler(NO ); }]]; [self presentViewController:alertController animated:YES completion:nil ]; } - (void )webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:webView.URL .host preferredStyle:UIAlertControllerStyleAlert ]; [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.text = defaultText; }]; [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString (@"【OK】" , nil ) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { NSString *input = ((UITextField *)alertController.textFields .firstObject ).text ; completionHandler(input); }]]; [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString (@"【Cancel】" , nil ) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { completionHandler(nil ); }]]; [self presentViewController:alertController animated:YES completion:nil ]; } @end
1、WKWebView初始化设定 上面源码的viewDidLoad
中我们可以看到WKWebView初始化设定用到的几个类:
WKPreferences 偏好设置
WKUserContentController 内容控制
WKWebViewConfiguration 配置
WKWebView初始化需要用到WKWebViewConfiguration的实例:
1 self .webView = [[WKWebView alloc] initWithFrame: self .view.bounds configuration: self .configuretion];
WKWebViewConfiguration需要通过WKPreferences和WKUserContentController实例来设置其包含的这两个属性:
1 2 self .configuretion.preferences = preferences;self .configuretion.user ContentController = user ContentController;
2、WKWebView加载本地HTML页面 和UIWebView相同,都是使用[self.webView loadHTMLString: htmlString baseURL: nil];
方法。详细内容在上面源码的loadLocalHtml
中。
3、WKWebView页面加载完毕后立刻执行本地js文件中的方法 和UIWebView相似,只是代理方法,以及执行js方法的名称不同。WKWebView相比于UIWebView的[myWebView stringByEvaluatingJavaScriptFromString:@"drawLinearGradient();"];
方法而言更加完善,增加了回调Block,可以在OC代码中捕捉到JS的错误及JS返回值:
1 2 3 [self.webView evaluateJavaScript:@"drawLinearGradient();" completionHandler:^(id data, NSError * error) { } ]
详细内容在上面源码的- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
代理方法中。
4、使用用户脚本来注入 JavaScript WKUserScript 对象可以以 JavaScript 源码形式初始化,初始化时还可以传入是在加载之前还是结束时注入,以及脚本影响的是这个布局还是仅主要布局。于是用户脚本被加入到 WKUserContentController 中,并且以 WKWebViewConfiguration 属性传入到 WKWebView 的初始化过程中。
这个样例可以简单扩展为更为高级的页面修改方法,例如去除广告、隐藏评论等。
1 2 3 WKUserScript *user Script2 = [[WKUserScript alloc] initWithSource:@"function changeMyName() { document.getElementById('myName').innerHTML='LYL\\'s Blog'; }" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd for MainFrameOnly:YES]; [self .configuretion.user ContentController addUserScript:user Script2];
详细内容在上面源码的testAddUserScript
中。
注:示例代码中,提到了可以直接注入js执行方法,能够达到evaluateJavaScript
的效果。经测试这种做法并不靠谱,有时不会执行(比如把这段代码挪到- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
方法中就不会执行),貌似和代码调用位置有关。为了保险起见,要执行JS方法,大家还是用应该使用evaluateJavaScript
方法。
5、WKWebView处理JS调用OC 相比UIWebView笨拙的处理方式,WKWebView处理JS调用OC的逻辑过程更加清晰明了。使用过程分如下3步:
(1)OC端通过WKUserContentController
的addScriptMessageHandler
注册一个名称,来匹配js的回调。
1 [self .configuretion.user ContentController addScriptMessageHandler:self name:@"TestJSMessage" ];
(2)js端需要固定用window.webkit.messageHandlers.<name>.postMessage();
来向OC发送消息。其中<name>
必须等于第(1)步中注册的名称,否则第(3)步代理方法不会执行。
1 2 3 4 5 function callOCMethord2 ( ) { window .webkit.messageHandlers.TestJSMessage.postMessage("这是JS传递的内容" ); }
(3)OC端用WKScriptMessageHandler protocal的- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
方法来接收消息。而方法内部,通过message.name
来判断代理方法中要处理哪个JS发出的OC请求。
详细内容在上面源码的testAddScriptMessageName
方法中。
5、使用WKUIDelegate处理JS的弹出框 WKWebView默认是不会弹出JS对话框的,如果您需要弹出JS对话框,必须设置WKUIDelegateself.webView.UIDelegate = self;
。WKUIDelegate提供了3个代理方法分别用来处理js的alert\confirm\prompt。
详细内容大家可以定位到#pragma mark - WKUIDelegate
来查看,代理方法的触发在- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
方法中。
资料:
WKWebView新特性及JS交互
WKWebView
Using JavaScript with WKWebView in iOS 8