前言:
web页面和app的直接的交互是很常见的东西,在ios8之前,用的是uiwebview,但是在ios8之后,苹果推出了WebKit这个框架,用来替代原有的UIWebView。以前的时候需要适配iOS7,现在扔掉了iOS7,所以在做和web页面的交互的时候,可以用wkwebview替代原有的uiwebview。
WKWebView的特点:
- 性能高,稳定性好,占用的内存比较小,
- 支持JS交互
- 支持HTML5 新特性
- 可以添加进度条。
- 支持内建手势,
- 据说高达60fps的刷新频率
使用及注意点
WKWebView只能用代码创建,而且自身就支持了右滑返回手势allowsBackForwardNavigationGestures和加载进度estimatedProgress等一些UIWebView不具备却非常好用的属性。在创建的时候,指定初始化方法中要求传入一个WKWebViewConfiguration对象,一般我们使用默认配置就好,但是有些地方是要根据自己的情况去做更改。比如,配置中的allowsInlineMediaPlayback这个属性,默认为NO,如果不做更改,网页中内嵌的视频就无法正常播放。
WKWebView的相关的代理方法
WKWebView的相关的代理方法分别在WKNavigationDelegate和WKUIDelegate以及WKScriptMessageHandler这个与JavaScript交互相关的代理方法。
- WKNavigationDelegate
该代理提供的方法,可以用来追踪加载过程(页面开始加载、加载完成、加载失败)、决定是否执行跳转。具体的使用可以看下面的代码示例 - WKUIDelegate
这个代理方法全都是与界面弹出提示框相关的,所以最好是实现的,否则的话,遇到网页alert的时候,如果此代理方法没有实现,则不会出现弹框提示。 - WKScriptMessageHandler
这个代理方法就是实现和JavaScript交互相关的。下面会介绍js调用oc,oc调用js的具体的使用。
具体的使用的过程
创建WKWebView
- 我这里是定义了全局的变量
@interface ACViewController ()<WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler>
@property (strong, nonatomic) WKWebView *wkwebView;
@property (strong, nonatomic) UIProgressView *progressView;//这个是加载页面的进度条
@end
@implementation ACViewController
{
UILabel * label ;
WKUserContentController * userContentController;
}
- 创建
#pragma mark 初始化webview
-(void)initWKWebView
{
WKWebViewConfiguration * configuration = [[WKWebViewConfiguration alloc]init];//先实例化配置类 以前UIWebView的属性有的放到了这里
//注册供js调用的方法
userContentController =[[WKUserContentController alloc]init];
//弹出登录
[userContentController addScriptMessageHandler:self name:@"loginVC"];
//加载首页
[userContentController addScriptMessageHandler:self name:@"gotoFirstVC"];
//进入详情页
[userContentController addScriptMessageHandler:self name:@"gotodetailVC"];
configuration.userContentController = userContentController;
configuration.preferences.javaScriptEnabled = YES;//打开js交互
_wkwebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0.0f, screen_width, screen_height) configuration:configuration];
_wkwebView.backgroundColor = [UIColor clearColor];
_wkwebView.UIDelegate = self;
_wkwebView.navigationDelegate = self;
_wkwebView.allowsBackForwardNavigationGestures =YES;//打开网页间的 滑动返回
_wkwebView.allowsLinkPreview = YES;//允许预览链接
[self initProgressView];
[_wkwebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];//注册observer 拿到加载进度
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:具体的网址]];
[_wkwebView loadRequest:request];
[self.view addSubview:_wkwebView];
}
```
- 关于进度条的监听
pragma mark --这个就是设置的上面的那个加载的进度
-(void)initProgressView
{
_progressView =[[UIProgressView alloc]initWithFrame:CGRectMake(0,0, screen_width, 10.0f)];
_progressView.tintColor = [UIColor blueColor];
_progressView.trackTintColor = [UIColor whiteColor];
[self.view addSubview:_progressView];
}
//这个是检测那个进度条的,显示完成之后,进度条就隐藏了
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if([keyPath isEqualToString:@"estimatedProgress"])
{
_progressView.hidden = NO;
CGFloat progress = [ change[@"new"] floatValue];
[_progressView setProgress:progress];
if(progress==1.0)
{
_progressView.hidden =YES;
}
}
}
- 和js交互
1.js调用oc的方法
WKScriptMessage有两个关键属性name 和 body。因为我们给每一个OC方法取了一个name,所以就可以根据name 来区分执行不同的方法。body 中存着JS 要给OC 传的参数。
在这里我是在前面注册了三个供js调用的方法,loginVC,gotoFirstVC,gotodetailVC。就是name。
在这个方法里面实现的
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{};
具体的代码如下
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if(message.name ==nil || [message.name isEqualToString:@""])
return;
//message body : js 传过来值
NSLog(@"message.body ==%@",message.body);
if ([message.name isEqualToString:@"loginVC"])
{
[self showLoginView];
}
else if ([message.name isEqualToString:@"gotoFirstVC"])
{
[self showFirstVC];
}
//进入详情页
else if ([message.name isEqualToString:@"gotodetailVC"])
{
NSString*url = [message.body objectForKey:@"body"];
[self gotodetial:url];
}
}
接下来就是具体的调用方法的实现了。
//弹出登陆页面
- (void)showLoginView
{
//具体看项目中的实现了
}
/**
跳转到首页
/
-(void)showFirstVC{
//具体看项目中的实现了
}
/*
跳转到详情页
*/
-(void)gotodetial:(NSString *)url {
//具体看项目中的实现了
}
2.oc调用js的方法,给js传值
oc调用js是通过evaluateJavaScript,这个方法里面的实现的
我这里处理的时候,是在页面加载完成的时候,oc给js传值
比如我需要给js传用户的登录状态和用户的信息,只要在加载完成的时候,调用方法,直接给值就行。
代码示例
//加载页面数据完成
-(void)webView:(WKWebView )webView didFinishNavigation:(null_unspecified WKNavigation )navigation
{
[_wkwebView evaluateJavaScript:@"document.getElementById("content").offsetHeight;" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
//获取页面高度,并重置webview的frame
CGFloat documentHeight = [result doubleValue];
CGRect frame = _wkwebView.frame;
frame.size.height = documentHeight+[UIScreen mainScreen].bounds.size.height-58 /显示不全/;
_wkwebView.frame = frame;
}];
NSString *js = [NSString stringWithFormat:@"isLogin(%d)",[[UserInfoSingleton shareUserInfoSingleton] isLogin]];
// NSLog(@"%@",message.name);
//[self.wkwebView evaluateJavaScript:js completionHandler:nil];
[self.wkwebView evaluateJavaScript:js completionHandler:^(id item, NSError * _Nullable error) {
}];
NSString *js1 = [NSString stringWithFormat:@"userId(\'%@\')",[UserInfoSingleton shareUserInfoSingleton].userId];
// [self.wkwebView evaluateJavaScript:js1 completionHandler:nil];
[self.wkwebView evaluateJavaScript:js1 completionHandler:^(id item, NSError * _Nullable error) {
}];
}
- 弹窗
代码示例
//弹窗
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
// DLOG(@"msg = %@ frmae = %@",message,frame);
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = defaultText;
}];
[alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text?:@"");
}])];
}
- wkwebview所有的代理方法
1.WKNavigationDelegate来追踪加载过程
// 页面开始加载时调用
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
// 当内容开始返回时调用
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
// 页面加载完成之后调用
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
// 页面加载失败时调用
-(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;
2. WKNavigtionDelegate来进行页面跳转
// 接收到服务器跳转请求之后再执行
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
// 在收到响应后,决定是否跳转
-(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 在发送请求之前,决定是否跳转
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
```
- WKUIDelegate
//1.创建一个新的WebVeiw
-(nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
//2.WebVeiw关闭(9.0中的新方法)
-(void)webViewDidClose:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
//3.显示一个JS的Alert(与JS交互)
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
//4.弹出一个输入框(与JS交互的)
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;
//5.显示一个确认框(JS的)
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
```
以上的所有的代理方法,根据自己的使用情况调用就行。
#注意
- Objective-C在回调JavaScript的时候,WKWebView没有办法传过来一个匿名函数,所以回调方式,要么执行一段JavaScript代码,或者就是调用JavaScript那边的一个全局函数。一般是采用后者,至于Web端虽说暴露了一个全局函数,同样可以把这一点代码处理的很优雅。Objective-C传给JavaScript的参数,可以为Number, String, and Object。参考如下:
// 数字
NSString *js = [NSString stringWithFormat:@"方法名(%@)", number];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 字符串
NSString *js = [NSString stringWithFormat:@"方法名('%@')", string];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 对象
NSString *js = [NSString stringWithFormat:@"方法名(%@)", @{@"name" : @"timefor"}];
[self.webView evaluateJavaScript:js completionHandler:nil];
// 带返回值的JS函数
[self.webView evaluateJavaScript:@"方法名()" completionHandler:^(id result, NSError * _Nullable error) {
// 接受返回的参数,result中
}];
```
- 我在本文中写的,都是根据自己项目中用到的写的,主要是在和h5交互的这块,互相传值,调用的,都是用的自己项目中的方法名。可能会好理解一些。
- 关于导航栏处理的之类的,这次项目中没用用到,所以没有做深入的研究,只是研究了项目中的问题。