iOS WKWebView的用法介绍,Native(OC)与JS
在开发过程中,iOS 中实现加载 web 页面主要有两种控件,UIWebView 和 WKWebview,两种控件对应具体的实现方法不同。WKWebView是苹果公司在iOS8系统推出的,这里主要概述WebKit中更新的WKWebView控件的新特性与使用方法,以及小编在开发过程中踩的坑。
一、WKWebView新特性
1.在性能、稳定性、功能方面有很大提升(最直观的体现就是加载网页是占用的内存,模拟器加载百度与开源中国网站时,WKWebView占用23M,而UIWebView占用85M);
2.允许JavaScript的Nitro库加载并使用(UIWebView中限制);
3.支持了更多的HTML5特性;
4.高达60fps的滚动刷新率以及内置手势;
5.将UIWebViewDelegate与UIWebView重构成了14类与3个协议。
6.KVO监听页面加载进度,添加进度条。
二、创建WKWebView并加载url
先引入头文件
#import <WebKit/WebKit.h>
然后创建webview
//配置
WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init];
WKUserContentController *userContentController = [[WKUserContentController alloc] init];//js与h5交互的类
[userContentController addScriptMessageHandler:self name:@"Share"];//添加ScriptMessageHandler
config.userContentController = userContentController;
WKPreferences *preferences = [WKPreferences new];
preferences.javaScriptCanOpenWindowsAutomatically = YES;//允许js交互
preferences.minimumFontSize = 40.0;
config.preferences = preferences;
//创建
_webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, Screen_Height - 64) configuration:config];
_webView.navigationDelegate = self;
_webView.UIDelegate = self;
//添加此属性可触发侧滑返回上一网页与下一网页操作
_webView.allowsBackForwardNavigationGestures = YES;
//KVO监听页面加载进度
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:NULL];
NSURL *url = [NSURL URLWithString:[_webUrlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
[_webView loadRequest:[[NSURLRequest alloc] initWithURL:url]];
KVO监听网页加载进度设置进度条(_loadingProgressView进度条创建就不赘述了哈)
//kvo监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"estimatedProgress"]) {//进度条
_loadingProgressView.progress = [change[@"new"] floatValue];
if (_loadingProgressView.progress == 1.0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.4f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
_loadingProgressView.hidden = YES;
});
}
}
}
-
注意:
1. 添加ScriptMessageHandler[config.userContentController addScriptMessageHandler:self name:@"Share"];
,引用了self,为了避免造成循环引用,有两个解决方法:
方法一:在页面消失的方法中需要移除ScriptMessageHandler,[_webView.configuration.userContentController removeScriptMessageHandlerForName:@"Share"];//移除ScriptMessageHandler
,否则不会走dealloc方法,self不会释放
方法二:重新创建一个代理对象,然后通过代理对象回调指定的self.
@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>
@property (nonatomic, assign) id<WKScriptMessageHandler> scriptDelegate;
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
@end
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate
{
self = [super init];
if (self) {
_scriptDelegate = scriptDelegate;
}
return self;
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
[self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}
@end
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"对象名"];
- (void)dealloc {
...
[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"对象名"];
...
}
2. 需要加载的url最好做一次encoding编码处理,开发过程中url包含中文出现过请求发不出去的情况
3. WKWebView中增加了WKWebViewConfiguration,WKUserContentController,用来配置页面属性,JS相关的一些东西
三、WKWebView的代理方法
1. WKNavigationDelegate
追踪加载过程的代理方法(页面开始加载、服务开始响应返回数据时、加载完成、加载失败)
// 2⃣️页面开始加载时调用
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
// 4⃣️当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
// 5⃣️页面加载完成之后调用
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
// 页面加载失败时调用
-(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;
页面跳转的代理方法有三种,分为(收到跳转与决定是否跳转两种):
//接收到服务器跳转请求之后调用 (服务器端redirect),不一定调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
// 3⃣️在收到响应后,决定是否跳转
-(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 1⃣️在发送请求之前,决定是否跳转
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
以上代理的方法执行的顺序:1⃣️->2⃣️->3⃣️->4⃣️->5⃣️
- 注意:只要实现关于是否跳转的协议方法,decisionHandler必须调用,来决定是否跳转,WKNavigationActionPolicyCancel,WKNavigationActionPolicyAllow。否则程序会crash
2. WKUIDelegate
WKUIDelegate主要是做跟网页交互的,可以显示javascript的一些alert或者Action,看起来跟自己做的一样的。不过这几个代理方法小编开发中没用到过哈,所以不知道有没有坑。
//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;
3. WKScriptMessageHandler(负责处理js和oc交互)
先贴上HTML掉用JS代码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8">
<script language="javascript">
//JS执行window.webkit.messageHandlers.Share.postMessage(<messageBody>)
function shareClick() {
window.webkit.messageHandlers.Share.postMessage({title:'测试分享的标题',content:'测试分享的内容',url:'https://github.com/maying1992'});
}
//分享回调结果显示
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="button" value="分享" onclick="shareClick()" />
<h1>回调展示区</h1>
<textarea id ="returnValue" type="value" rows="5" cols="40">
</textarea>
</body>
</html>
首先客户端通过- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
添加了ScriptMessageHandler。例如:[config.userContentController addScriptMessageHandler:self name:@"Share"];
然后在HTML中JS 执行window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
时,OC端被添加的ScriptMessageHandler就会执行实现的WKScriptMessageHandler协议的方法 即
-(void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message
{
//JS调用OC方法
//message.boby就是JS里传过来的参数
if ([message.name isEqualToString:@"Share"]) {
NSLog(@"body:%@",message.body);
}
//OC调用JS,回传结果
NSString *JSResult = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
[_webView evaluateJavaScript:JSResult completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@", error);
}];
}
- 注:
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
的name 和- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
的name保持一致
总结:
简单介绍了WKWebView的创建、加载、属性配置以及JS和
OC交互的一些使用心得,包括已经踩过的坑,后续后继续补充,如有描述不当或者错误的地方,在评论区给我指出来,小编马不停蹄的更正哈