前言
在开发IOS应用的过程中,难免的会遇到和WebView打交道的场景,通常为了实现产品经理的功能需求还要去和WebView里面的JS进行交互。作为一个刚IOS开发的新人来说,第一次肯定会遇到各种问题和各种坑。在这里我把我的问题和解决方法罗列出来,希望对其他的大胸弟们有所帮助。
概述
IOS提供给我们的WebView控件类型总共有3中,UIWebView
,WkWebView
,SFSafariView
。这三种类型的控件有这不同的使用限制和要求,在实际项目中,我们需要根据项目的实际要求选择其中的一个就可以了。接下来,我会介绍一下这三种类型的区别。
UIWebView
UIWebView作为IOS应用提供给开发者最早的一个控件,在当时算是一个很不错的控件,但是基于今天的现状而言,却显得有些鸡肋了。加载速度慢,内存开销大对于产品和开发者来说都是很大的问题,特别是在第一次加载网页时,经常会出现加载10秒以上的情况。
UIWebView加载过程
UIWebView加载网页主要会用到一下四个方法
1
| - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
|
当WebView收到一个打开链接请求时,会触发此方法,向我们询问是否要加载这个链接请求,返回YES
就表示允许加载这个链接请求,否则不加载链接请求。
1
| - (void) webViewDidStartLoad:(UIWebView *)webView
|
当上一步允许加载链接请求后,WebView会触发此方法,告知我们要开始加载了,这时我们可以加一个loading的效果提示用户。
1
| - (void) webViewDidFinishLoad:(UIWebView *)webView
|
当整个网页加载完成之后会触发此方法,如果上一步加上loading效果的话,在这个就要去掉了
1
| - (void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
|
当加载过程中出错,比如无网络、404什么的,会执行这个方法。这里我们要把loading去掉,如果为了友好的话,可以展示一个加载失败的界面。
UIWebView和JS交互
UIWebView和JS交互主要依托于一个JSExport
的协议,具体来说就是在加载网页的过程中,我们去拿到网页运行JS的环境,然后JS执行某些方法时我们就可以捕获到,然后执行自己的逻辑。
注意:和JS的交互过程中需要分两种情况,一种是捕获JS中无参数或一种参数的方法,另一种是捕获JS中大于等于2个参数的方法。因为OC中的方法名是由传统意义上的方法名+外部参数名构成的,所以当我们捕获第二种情况的JS方法时需要注意和OC方法名的对应关系。下面的示例中会有介绍。
OK,接下来给大家放出一段示例代码,示例中会讲解到UIWebView和加载一个网页的流程,以及网页的JS如何和原生的代码交互。
ViewController.h
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
| // // ViewController.h // webviewDemo // // Created by 孙天文 on 16/11/15. // Copyright © 2016年 孙天文. All rights reserved. //
#import <UIKit/UIKit.h> #import <JavaScriptCore/JavaScriptCore.h>
//定义一个和JS交互的协议,用来处理JS中的方法,这个协议中列出了4个方法,分别是无参数、一个参数、两个参数以及三个参数的方法 @protocol demoJsResponseProtocol <JSExport>
- (void) sayHello;
- (void) setTitle:(NSString *)title;
//下面是大于等于两个参数的情况,要注意这个格式和JS中的格式对比 - (void) setTitle:(NSString *)title Left:(NSString *)leftTitle;
- (void) setTitle:(NSString *)title Left:(NSString *)leftTitle Right:(NSString *)rightTitle;
@end
@interface ViewController : UIViewController<demoJsResponseProtocol>
@end
|
ViewController.m
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
| // // ViewController.m // webviewDemo // // Created by 孙天文 on 16/11/15. // Copyright © 2016年 孙天文. All rights reserved. //
#import "ViewController.h"
@interface ViewController ()<UIWebViewDelegate,JSExport> @property (strong,nonatomic) UIWebView *webView; //JS运行环境 @property (strong,nonatomic) JSContext *jsContext;
@end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; self.webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)]; self.webView.delegate = self; //加载本地html文件 NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"]; NSString *htmlString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; NSString *basePath = [[NSBundle mainBundle] bundlePath]; NSURL *baseURL = [NSURL fileURLWithPath:basePath]; [self.webView loadHTMLString:htmlString baseURL:baseURL]; [self.view addSubview:self.webView]; } //网页加载前调用的方法 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ return YES; } //开始加载 - (void) webViewDidStartLoad:(UIWebView *)webView{ NSLog(@"start laod"); } //加载完成 - (void) webViewDidFinishLoad:(UIWebView *)webView{ self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; self.jsContext[@"demoapi"] = self; NSLog(@"laod finish"); }
//加载出错 - (void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{ NSLog(@"load error %@",error); } //执行JS方法 - (void) doJavaScriptFunction:(NSString *)jsfunction{ [self.jsContext evaluateScript:jsfunction]; }
- (void) sayHello{ [self showAlertDesc:@"hello world"]; } - (void) setTitle:(NSString *)title{ [self showAlertDesc:@"setTitle"]; } //下面是大于等于两个参数的情况,要注意这个格式和JS中的格式对比 - (void) setTitle:(NSString *)title Left:(NSString *)leftTitle{ [self showAlertDesc:@"setTitleLeft"]; } - (void) setTitle:(NSString *)title Left:(NSString *)leftTitle Right:(NSString *)rightTitle{ [self showAlertDesc:@"setTitleLeftRight"]; } - (void) showAlertDesc:(NSString *)desc{ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:desc preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; [alertController addAction:okAction]; [self presentViewController:alertController animated:YES completion:nil]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. }
@end
|
test.html
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
| <!DOCTYPE html> <html> <head> <style> .button{ background-color: #d9edf7!important; display:block; margin-top:40px; height:30px; } </style> </head> <body> <a href = "#" class = "button" onclick = "sayHello()">Say Hello</a> <a href = "#" class = "button" onclick = "setTitle()">Set Title</a> <a href = "#" class = "button" onclick = "setTitle2()">Set Title Left</a> <a href = "#" class = "button" onclick = "setTitle3()">Set Title Left Right</a> </body> <script type="text/javascript"> function sayHello(){ demoapi.sayHello(); } function setTitle(){ demoapi.setTitle("Title1"); } function setTitle2(){ demoapi.setTitleLeft("Title2","LeftTitle2"); } function setTitle3(){ demoapi.setTitleLeftRight("Title3","LeftTitle3","RightTitle3"); } </script> </html>
|
WKWebView
WKWebView作为苹果官方推荐的Web控件,在UIWebView的基础上进行重构,加载速度和内存开销上都有很大的提升,当我们需要集成该控件时,需要去注意改控件提供和方法和UIWebView的不同。需要注意一点,这个控件要求系统版本最低是IOS8,如果你的项目需要覆盖IOS8一下的用户的话,还是乖乖的去用UIWebView吧。
详细情况后续补充
SFSafariView
SFSafariView给人直观的印象是把Safari内嵌到了APP当中,目前笔者还没有接触过,这里就不在叙述更多的信息了,如果后面结果的话,会再写一篇博客进行详细说明。