Date
Nov. 23rd, 2024
 
2024年 10月 23日

Post: iOS: WKWebView Useage

iOS: WKWebView Useage

Published 12:11 Nov 01, 2015.

Created by @ezra. Categorized in #Programming, and tagged as #iOS.

Source format: Markdown

Table of Content

iOS8以后, 苹果推出了新框架Wekkit, 提供了替换UIWebView的组件WKWebView。各种UIWebView的问题没有了, 速度更快了, 占用内存少了, 一句话, WKWebView是App内部加载网页的最佳选择!

先看下 WKWebView的特性:

  1. 在性能、稳定性、功能方面有很大提升
  2. 允许JavaScript的Nitro库加载并使用;
  3. 支持了更多的HTML5特性;
  4. 高达60fps的滚动刷新率以及内置手势;
  5. 将UIWebViewDelegate与UIWebView重构成了14类与3个协议 (查看苹果官方文档) ;

然后从以下几个方面说下WKWebView的基本用法:

  1. 加载网页
  2. 加载的状态回调
  3. 新的WKUIDelegate协议
  4. 动态加载并运行JS代码
  5. webView 执行JS代码
  6. JS调用App注册过的方法

一、加载网页

加载网页或HTML代码的方式与UIWebView相同, 代码示例如下:

WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]]];
[self.view addSubview:webView];

二、加载的状态回调 (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;

页面跳转的代理方法:

// 接收到服务器跳转请求之后调用

- (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协议

这个协议主要用于WKWebView处理web界面的三种提示框(警告框、确认框、输入框), 下面是警告框的例子:

/**

- web界面中有弹出警告框时调用 *
- @param webView 实现该代理的webview
- @param message 警告框中的内容
- @param frame 主窗口
- @param completionHandler 警告框消失调用
*/

- (void)webView:(WKWebView _)webView runJavaScriptAlertPanelWithMessage:(NSString_ )message initiatedByFrame:(void (^)())completionHandler;

四、动态加载并运行JS代码

用于在客户端内部加入JS代码, 并执行, 示例如下:

// 图片缩放的js代码
NSString *js = @"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '张图');";
// 根据JS字符串初始化WKUserScript对象
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
// 根据生成的WKUserScript对象, 初始化WKWebViewConfiguration
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:script];
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[_webView loadHTMLString:@"<head></head><imgea src='https://www.nsu.edu.cn/v/2014v3/img/background/3.jpg' />"baseURL:nil];
[self.view addSubview:_webView];

五、webView 执行JS代码

用户调用用JS写过的代码, 一般指服务端开发的:

//javaScriptString是JS方法名, completionHandler是异步回调block
[self.webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];

scriptMessageHandler是代理回调, JS调用name方法后, OC会调用scriptMessageHandler指定的对象。

JS在调用OC注册方法的时候要用下面的方式:

window.webkit.messageHandlers.<name>.postMessage(<messagebody>)

注意, name(方法名)是放在中间的, messageBody只能是一个对象, 如果要传多个值, 需要封装成数组, 或者字典。整个示例如下:

// OC注册供JS调用的方法
[[_webView configuration].userContentController addScriptMessageHandler:self name:@"closeMe"];

// OC在JS调用方法做的处理
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSLog(@"JS 调用了 %@ 方法, 传回参数 %@",message.name,message.body);
}

// JS调用
window.webkit.messageHandlers.closeMe.postMessage(null);

如果你在selfdealloc打个断点, 会发现self没有释放!这显然是不行的!谷歌后看到一种解决方法, 如下:

@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>

@property (nonatomic, weak) 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

思路是另外创建一个代理对象, 然后通过代理对象回调指定的self,

WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"closeMe"];

运行代码, self释放了, WeakScriptMessageDelegate却没有释放啊啊啊!
还需在selfdealloc里面 添加这样一句代码:

[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"closeMe"];

至此, 问题解决。

目前大多数App需要支持iOS7以上的版本, 而WKWebView只在iOS8后才能用, 所以需要一个兼容性方案, 既iOS7下用UIWebView, iOS8后用WKWebView。兼容性方案可以参考: IMYWebView

Pinned Message
HOTODOGO
The Founder and CEO of Infeca Technology.
Developer, Designer, Blogger.
Big fan of Apple, Love of colour.
Feel free to contact me.
反曲点科技创始人和首席执行官。
开发、设计与写作皆为所长。
热爱苹果、钟情色彩。
随时恭候 垂询