第一部分 WKWebView介绍(转)
问:为什么有UIWebView还要推出WKWebView?
因为使用UIWebView加载网页的时候,会有内存无限增长,还有内存泄漏等问题存在。WebKit中更新的WKWebView控件的新特性与使用方法,很好的解决了UIWebView存在的内存、加载速度等诸多问题。
一、WKWebView新特性
-
在性能、稳定性、功能方面有很大提升(最直观的体现就是加载网页是占用的内存);
-
允许JavaScript的Nitro库加载并使用(UIWebView中限制);
-
支持了更多的HTML5特性;
-
高达60fps的滚动刷新率以及内置手势;
将UIWebViewDelegate与UIWebView重构成了14类与3个协议查看苹果官方文档;
二、WebKit框架概览
image如上图所示,WebKit框架中最核心的类应该属于WKWebView了,这个类专门用来渲染网页视图,其他类和协议都将基于它和服务于它。
-
WKWebView:网页的渲染与展示,通过WKWebViewConfiguration可以进行自定义配置。
-
WKWebViewConfiguration:这个类专门用来配置WKWebView。
-
WKPreference:这个类用来进行相关webView设置。
-
WKProcessPool:这个类用来配置进程池,与网页视图的资源共享有关。
-
WKUserContentController:这个类主要用来做native与JavaScript的交互管理。
-
WKUserScript:用于进行JavaScript注入。
-
WKScriptMessageHandler:这个类专门用来处理JavaScript调用native的方法。
-
WKNavigationDelegate:网页跳转间的导航管理协议,这个协议可以监听网页的活动。
-
WKNavigationAction:网页某个活动的示例化对象。
-
WKUIDelegate:用于交互处理JavaScript中的一些弹出框。
-
WKBackForwardList:堆栈管理的网页列表。
-
WKBackForwardListItem:每个网页节点对象。
三、WKWebView的属性
/// webView的自定义配置
@property (nonatomic,readonly, copy) WKWebViewConfiguration *configuration;
/// 导航代理
@property (nullable, nonatomic, weak)id navigationDelegate;
/// UI代理
@property (nullable, nonatomic, weak)id UIDelegate;
/// 访问过网页历史列表
@property (nonatomic,readonly, strong) WKBackForwardList *backForwardList;
/// 自定义初始化
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
/// url加载webView视图
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
/// 文件加载webView视图
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
/// HTMLString字符串加载webView视图
- (nullable WKNavigation *)loadHTMLString:(NSString *)stringbaseURL:(nullable NSURL *)baseURL;
/// NSData数据加载webView视图
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));
/// 返回上一个网页节点
- (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;
/// 网页的标题
@property (nullable, nonatomic,readonly, copy) NSString *title;
/// 网页的URL地址
@property (nullable, nonatomic,readonly, copy) NSURL *URL;
/// 网页是否正在加载
@property (nonatomic,readonly, getter=isLoading) BOOL loading;
/// 加载的进度 范围为[0, 1]
@property (nonatomic,readonly)double estimatedProgress;
/// 网页链接是否安全
@property (nonatomic,readonly) BOOL hasOnlySecureContent;
/// 证书服务
@property (nonatomic,readonly, nullable) SecTrustRef serverTrust API_AVAILABLE(macosx(10.12), ios(10.0));
/// 是否可以返回
@property (nonatomic,readonly) BOOL canGoBack;
/// 是否可以前进
@property (nonatomic,readonly) BOOL canGoForward;
/// 返回到上一个网页
- (nullable WKNavigation *)goBack;
/// 前进到下一个网页
- (nullable WKNavigation *)goForward;
/// 重新加载
- (nullable WKNavigation *)reload;
/// 忽略缓存 重新加载
- (nullable WKNavigation *)reloadFromOrigin;
/// 停止加载
- (void)stopLoading;
/// 执行JavaScript
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void(^ _Nullable)(_Nullableid, NSError * _Nullable error))completionHandler;
/// 是否允许左右滑动,返回-前进操作 默认是NO
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;
/// 自定义代理字符串
@property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0));
/// 在iOS上默认为NO,标识不允许链接预览
@property (nonatomic) BOOL allowsLinkPreview API_AVAILABLE(macosx(10.11), ios(9.0));
/// 滚动视图
@property (nonatomic,readonly, strong) UIScrollView *scrollView;
/// 是否支持放大手势,默认为NO
@property (nonatomic) BOOL allowsMagnification;
/// 放大因子,默认为1
@property (nonatomic) CGFloat magnification;
/// 据设置的缩放因子来缩放页面,并居中显示结果在指定的点
- (void)setMagnification:(CGFloat)magnification centeredAtPoint:(CGPoint)point;/// 证书列表@property (nonatomic,readonly, copy) NSArray *certificateChain API_DEPRECATED_WITH_REPLACEMENT("serverTrust", macosx(10.11,10.12), ios(9.0,10.0));
四、WKWebView的使用
- 简单使用,直接加载url地址
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://developer.apple.com/reference/webkit"]]];
[self.view addSubview:webView];
- 自定义配置,给WKWebViewConfiguration的WKUserContentController赋值下面的样式,使显示样式正常好看
- (WKUserContentController *)wkwebViewScalPreferences {
NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
[wkUController addUserScript:wkUScript];
return wkUController;
}
五、WKNavigationDelegate代理方法
如果实现了代理方法,一定要在decidePolicyForNavigationAction
和decidePolicyForNavigationResponse
方法中的回调设置允许跳转。
typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
WKNavigationActionPolicyCancel, // 取消跳转
WKNavigationActionPolicyAllow, // 允许跳转
} API_AVAILABLE(macosx(10.10), ios(8.0));
- 1 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler {
NSLog(@"1-------在发送请求之前,决定是否跳转 -->%@",navigationAction.request);
decisionHandler(WKNavigationActionPolicyAllow);
}
- 2 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"2-------页面开始加载时调用");
}
- 3 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void(^)(WKNavigationResponsePolicy))decisionHandler {
/// 在收到服务器的响应头,根据response相关信息,决定是否跳转。decisionHandler必须调用,来决定是否跳转,参数WKNavigationActionPolicyCancel取消跳转,WKNavigationActionPolicyAllow允许跳转 NSLog(@"3-------在收到响应后,决定是否跳转");
decisionHandler(WKNavigationResponsePolicyAllow);
}
- 4 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSLog(@"4-------当内容开始返回时调用");
}
- 5 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSLog(@"5-------页面加载完成之后调用");
}
- 6 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"6-------页面加载失败时调用");
}
- 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"-------接收到服务器跳转请求之后调用");
}
- 数据加载发生错误时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"----数据加载发生错误时调用");
}
- 需要响应身份验证时调用 同样在block中需要传入用户身份凭证
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void(^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
//用户身份信息 NSLog(@"----需要响应身份验证时调用 同样在block中需要传入用户身份凭证");
NSURLCredential *newCred = [NSURLCredential credentialWithUser:@"" password:@"" persistence:NSURLCredentialPersistenceNone];
// 为 challenge 的发送方提供 credential [[challenge sender] useCredential:newCred forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}
- 进程被终止时调用
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
NSLog(@"----------进程被终止时调用");
}
六、WKUIDelegate代理方法
/**
* web界面中有弹出警告框时调用
*
* @param webView 实现该代理的webview
* @param message 警告框中的内容
* @param completionHandler 警告框消失调用
*/
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void(^)())completionHandler {
NSLog(@"-------web界面中有弹出警告框时调用");
}
* 创建新的webView时调用的方法
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
NSLog(@"-----创建新的webView时调用的方法");
return webView;
}
// 关闭webView时调用的方法
- (void)webViewDidClose:(WKWebView *)webView {
NSLog(@"----关闭webView时调用的方法");
}
// 下面这些方法是交互JavaScript的方法
// JavaScript调用confirm方法后回调的方法 confirm是js中的确定框,需要在block中把用户选择的情况传递进去
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void(^)(BOOL))completionHandler {
NSLog(@"%@",message);
completionHandler(YES);
}
// JavaScript调用prompt方法后回调的方法 prompt是js中的输入框 需要在block中把用户输入的信息传入
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void(^)(NSString * _Nullable))completionHandler{
NSLog(@"%@",prompt);
completionHandler(@"123");
}
// 默认预览元素调用
- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo {
NSLog(@"-----默认预览元素调用");
return YES;
}
// 返回一个视图控制器将导致视图控制器被显示为一个预览。返回nil将WebKit的默认预览的行为。
- (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray> *)previewActions {
NSLog(@"----返回一个视图控制器将导致视图控制器被显示为一个预览。返回nil将WebKit的默认预览的行为。");
return self;
}
// 允许应用程序向它创建的视图控制器弹出
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController {
NSLog(@"----允许应用程序向它创建的视图控制器弹出");
}
// 显示一个文件上传面板。completionhandler完成处理程序调用后打开面板已被撤销。通过选择的网址,如果用户选择确定,否则为零。如果不实现此方法,Web视图将表现为如果用户选择了取消按钮。
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void(^)(NSArray * _Nullable URLs))completionHandler {
NSLog(@"----显示一个文件上传面板");
}
第二部分 WKWebView与JS交互
一、传统交互方式
js调oc:响应WKNavigationDelegate代理方法,解析协议
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
NSString *scheme = url.scheme;
if ([scheme isEqualToString:@"lgedu"]) {
NSString *host = [url host];
if ([host isEqualToString:@"jsCallOC"]) {
NSMutableDictionary *temDict = [self decoderUrl:url];
NSString *username = [temDict objectForKey:@"username"];
NSString *password = [temDict objectForKey:@"password"];
NSLog(@"%@---%@",username,password);
}else {
NSLog(@"不明地址");
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
oc调js:使用evaluateJavaScript方法且有回调
- (void)login {
NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"登陆成功"];
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"%@----%@",result, error);
}];
}
showAlert()是js端定义的方法:
function showAlert(messgae){
alert('我是一个可爱的弹框 \n'+messgae+'\n'+arr[1]);
return "token";
}
二、使用事先注册Handler.Name的方式
给当前webView的配置userContentController设置ScriptMessageHandler的name,注册供js调用的方法
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"messgaeOC"];
注意:上面将当前ViewController设置为MessageHandler之后需要在当前ViewController销毁前将其移除,否则会造成内存泄漏。
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"NativeMethod"];
响应WKScriptMessageHandler代理方法,根据message.name区分,分别进行业务逻辑处理
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
// 根据 message.name判断
if (message.name) {
// OC 层面的消息,业务处理等
}
NSLog(@"message == %@ --- %@",message.name, message.body);
}
在js端需要调用(有点类似js与android交互)
function messgaeHandle(){
window.webkit.messageHandlers.messgaeOC.postMessage("SPIREJ 消息");
}
文中所有示例demo可以在这里下载
https://github.com/SPIREJ/OCJS
网友评论