替換WKWebView的原因有:
內(nèi)存占用少
項目h5較多墓怀,發(fā)現(xiàn)當(dāng)有高清圖或gif時在4s上crash較多,幾乎都是內(nèi)存原因卫键。WKWebView占用內(nèi)存較少傀履,立馬簡單的測試下,原本必掛的網(wǎng)頁運行正常莉炉,決定換钓账。
#import <WebKit/WebKit.h>
- (void)viewDidLoad {
[super viewDidLoad];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.view addSubview:_webView];
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:htmlPath]];
[_webView loadRequest:request];
...
}
進度容易獲取碴犬,無需引用第三方框架
首先看下,WKWebView的estimatedProgress屬性注解
/*! @abstract An estimate of what fraction of the current navigation has been completed.
@discussion This value ranges from 0.0 to 1.0 based on the total number of
bytes expected to be received, including the main document and all of its
potential subresources. After a navigation completes, the value remains at 1.0
until a new navigation starts, at which point it is reset to 0.0.
@link WKWebView @/link is key-value observing (KVO) compliant for this
property.
*/
注冊通知:
- (void)viewDidLoad
[super viewDidLoad];
...
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
響應(yīng)官扣,progressView用的是開源的NJKWebViewProgressView:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self.webView && [keyPath isEqualToString:@"estimatedProgress"]) {
CGFloat newprogress = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
GTRWeakSelf
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.webViewProgressView setProgress:newprogress animated:YES];
});
}
}
移除:
- (void)dealloc {
[_webView removeObserver:self forKeyPath:@"estimatedProgress"];
}
js與native通訊方式多樣化翅敌,和android統(tǒng)一,但不支持javascript core
1惕蹄、js使用alert蚯涮、prompt和confirm等方式,wkwebview用
native:
#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提醒" message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}]];
[self presentViewController:alert animated:YES completion:nil];
}
2卖陵、URL方式遭顶,如果地址是網(wǎng)絡(luò)上,可把html放到本地可解決
js:
<script language="javascript">
function loadURL(url) {
var iFrame;
iFrame = document.createElement("iframe");
iFrame.setAttribute("src", url);
iFrame.setAttribute("style", "display:none;");
iFrame.setAttribute("height", "0px");
iFrame.setAttribute("width", "0px");
iFrame.setAttribute("frameborder", "0");
document.body.appendChild(iFrame);
// 發(fā)起請求后這個iFrame就沒用了泪蔫,所以把它從dom上移除掉
iFrame.parentNode.removeChild(iFrame);
iFrame = null;
}
function getVersion() {
loadURL("gtrAction://getVersion");
}
</script>
native:
#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:@"haleyaction"]) {
[self handleCustomAction:URL];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
3棒旗、用MessageHandler方式,我們選擇這種方式實現(xiàn)撩荣。
js實現(xiàn):
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
native實現(xiàn)祥見集成問題實現(xiàn)铣揉。
集成問題
WKWebView使用有循環(huán)引用,原因是UIViewController->WKWebView->WKWebViewConfiguration->WKUserContentController餐曹,最后WKUserContentController在addScriptMessageHandler:name:又引用UIViewController逛拱。
有2個解決方案:
1、這種方式簡單台猴,只需在viewWillAppear和viewWillDisappear相應(yīng)的添加和刪除messageHandler朽合。不過部分系統(tǒng)上再次addScriptMessageHandler無效。
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"getVersion"];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[_webView.configuration.userContentController removeScriptMessageHandlerForName:@"getVersion"];
}
- (void)dealloc {
DebugLog(@"GTRViewController dealloc");
}
2饱狂、打破addScriptMessageHandler這個循環(huán)引用曹步,用中間代理方式實現(xiàn)。
GTRWeakScriptMessageDelegate.h
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
@protocol GTRWeakScriptMessageDelegate <NSObject>
- (void)getVersion;
@end
@interface GTRWeakScriptMessageDelegate : NSObject <WKScriptMessageHandler>
@property (nonatomic, weak) id <GTRWeakScriptMessageDelegate> gDelegate;
- (instancetype)initWithDelegate:(id <GTRWeakScriptMessageDelegate>)delegate;
@end
GTRWeakScriptMessageDelegate.m
#import "GTRWeakScriptMessageDelegate.h"
@interface GTRWeakScriptMessageDelegate ()
@end
@implementation GTRWeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id <GTRWeakScriptMessageDelegate>)delegate {
self = [super init];
if (self) {
self.gDelegate = delegate;
}
return self;
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if (![self.gDelegate conformsToProtocol:@protocol(GTRWeakScriptMessageDelegate)]) {
return;
}
if ([message.name isEqualToString:@"getVersion"]) {
//異步回掉
[self.gDelegate getVersion];
}
}
- (void)dealloc {
DebugLog(@"GTRWeakScriptMessageDelegate dealloc");
}
@end
GTRViewController.m
@property (nonatomic, strong) GTRWeakScriptMessageDelegate *weakScriptDelegate;
- (void)viewDidLoad {
[super viewDidload];
_weakScriptDelegate = [[GTRWeakScriptMessageDelegate alloc] initWithDelegate:self];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
//添加messageHandler
[_webView.configuration.userContentController addScriptMessageHandler:self.weakScriptDelegate name:@"getVersion"];
_webView.scrollView.delegate = self;
_webView.navigationDelegate = self;
_webView.UIDelegate = self;
[self.view addSubview:_webView];
}
- (void)dealloc {
[_webView.configuration.userContentController removeScriptMessageHandlerForName:@"getVersion"];
[_webView removeObserver:self forKeyPath:@"estimatedProgress"];
_webView.UIDelegate = nil;
_webView.navigationDelegate = nil;
_webView.scrollView.delegate = nil;
DebugLog(@"GTRViewController dealloc");
}
附送wkwebview修改Agent休讳,用來標(biāo)示是應(yīng)用內(nèi)web讲婚,ios9后用setCustomUserAgent:方法.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString *userAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
NSString *newUserAgent = [userAgent stringByAppendingString:@" ua gtr_demo"];//自定義需要拼接的字符串
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newUserAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
[[NSUserDefaults standardUserDefaults] synchronize];
}
GTRViewController.m
- (void)viewDidLoad {
...
[self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
DebugLog(@"Webview UserAgent:%@", result);
}];
...
}