UIWebView废弃,迁移WKWebView
WWDC 2018中 ,在安全方面,Session上来就宣布了一件重量级的大事,UIWebView正式被官方宣布废弃,建议开发者迁移适配到WKWebView。
在XCode9中UIWebView还是 NS_CLASS_AVAILABLE_IOS(2_0),而我们从最新的Xcode10再看UIWebView就已经是这个样子了
UIKIT_EXTERN API_DEPRECATED("No longer supported; please adopt WKWebView.", ios(2.0, 12.0)) API_PROHIBITED(tvos, macos) @interface UIWebView : UIView <NSCoding, UIScrollViewDelegate>
WKWebView从诞生之初相比UIWebView有太多的优势,无论是内存泄露还是网页性能,并且WKWebView可以同时支持macOS与iOS。由于WKWebView的独特设计,网页运行在独立的进程,如果网页遇到Crash,不会影响App的正常运行。
但是WKWebView不支持JSContext,不支持NSURLProtocol,Cookie管理蛋疼等问题确实给让不少开发者不想丢弃UIWebView,但最后通牒来了还是准备着手替换吧。
下面的一些方法均是看了许多大神的博客后总结到一起的,自己并未一一验证,后续发现错误会去纠正。
WKWebView的特点
- 性能高,稳定性好,占用的内存比较小,
- 支持JS交互
- 支持HTML5 新特性
- 可以添加进度条(然并卵,不好用,还是习惯第三方的)。
- 支持内建手势,
- 据说高达60fps的刷新频率(不卡)
初始化WKWebView
一、先导入头文件 #import <WebKit/WebKit.h>
二、WKWebView创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; config.selectionGranularity = WKSelectionGranularityDynamic; config.allowsInlineMediaPlayback = YES; WKPreferences *preferences = [WKPreferences new];
|
- WKWebViewConfiguration 用于配置WKWebView的一些属性
- WKPreferences 用于配置WKWebView视图的一些属性
- 加上
<WKNavigationDelegate, WKUIDelegate>
两个代理
三、WKNavigationDelegate代理事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
|
|
四、WKUIDelegate代理事件,主要实现与js的交互
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
|
五、JS调用OC方法
window.webkit.messageHandlers.方法名.postMessage(参数);
- 在WKScriptMessageHandler中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ if ([message.name isEqualToString:@"takePicturesByNative"]) { [self takePicturesByNativeWithBody:message.body]; } } - (void)takePicturesByNativeWithBody:(NSString *)body { NSLog(@"调用了takePicturesByNative方法"); UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:body preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *a1 = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}]; [alert addAction:a1]; [self presentViewController:alert animated:YES completion:nil]; }
|
在使用上述方法中发现,addScriptMessageHandler:self
中发生了循环引用,造成webview不会被释放掉,故经测试有以下两种解决方案:
1.新建个WeakScriptMessageDelegate类
1 2 3 4 5 6 7 8 9 10 11
|
#import <Foundation/Foundation.h> #import <WebKit/WebKit.h>
@interface WeakScriptMessageDelegate : NSObject <WKScriptMessageHandler>
@property (nonatomic, assign) id<WKScriptMessageHandler> scriptDelegate;
+ (instancetype)scriptWithDelegate:(id<WKScriptMessageHandler>)delegate;
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate { self = [super init]; if (self) { _scriptDelegate = scriptDelegate; } return self; }
+ (instancetype)scriptWithDelegate:(id<WKScriptMessageHandler>)delegate { return [[WeakScriptMessageDelegate alloc]initWithDelegate:delegate]; }
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message]; }
@end
|
设置addScriptMessageHandler方法更换为:
1 2
|
[[_webView configuration].userContentController addScriptMessageHandler:[WeakScriptMessageDelegate scriptWithDelegate:self] name:@"takePicturesByNative"];
|
2.不在初始化时添加ScriptMessageHandler, 而是和Notificenter/KVC一个思路
1 2 3 4 5 6 7 8 9 10 11 12 13
|
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated];
[_webView.configuration.userContentController addScriptMessageHandler:self name:@"takePicturesByNative"]; }
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated];
[_webView.configuration.userContentController removeScriptMessageHandlerForName:@"takePicturesByNative"];
}
|
六、OC调用JS方法
七、给webview添加请求头
1 2 3 4 5 6
|
NSString *urlString = @"https://www.baidu.com/"; NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"123" forHTTPHeaderField:@"token"]; [self.webView loadRequest:request];
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
|
注:在UIWeb里边是直接用的request 但是在这里需要写上navigationAction.出来的request
八、WKWebView加载不受信任的https
解决方法:在plist文件中设置Allow Arbitrary Loads in Web Content 置为 YES,并实现wkwebView下面的代理方法,就可解决
1 2 3 4 5 6 7 8 9 10 11
|
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential,card); } }
|
九、监听WKWebView的进度条和标题
1 2 3 4 5 6 7
|
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; [self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
|
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@property (nonatomic, weak) CALayer *progressLayer;
UIView *progress = [[UIView alloc]init]; progress.frame = CGRectMake(0, 0, KScreenWidth, 3); progress.backgroundColor = [UIColor clearColor]; [self.view addSubview:progress]; CALayer *layer = [CALayer layer]; layer.frame = CGRectMake(0, 0, 0, 3); layer.backgroundColor = [UIColor greenColor].CGColor; [progress.layer addSublayer:layer]; self.progressLayer = layer;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
#pragma mark - KVO回馈
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"estimatedProgress"]) { self.progressLayer.opacity = 1; if ([change[@"new"] floatValue] <[change[@"old"] floatValue]) { return; } self.progressLayer.frame = CGRectMake(0, 0, KScreenWidth*[change[@"new"] floatValue], 3); if ([change[@"new"]floatValue] == 1.0) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.progressLayer.opacity = 0; self.progressLayer.frame = CGRectMake(0, 0, 0, 3); }); } }else if ([keyPath isEqualToString:@"title"]){ self.title = change[@"new"]; } }
|
十、解决cookie问题
以前UIWebView会自动去NSHTTPCookieStorage中读取cookie,但是WKWebView并不会去读取,因此导致cookie丢失以及一系列问题,解决方式就是在request中手动帮其添加上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
self.webView.UIDelegate = self; self.webView.navigationDelegate = self; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.test.com"]]; [request addValue:[self readCurrentCookieWithDomain:@"http://www.test.com/"] forHTTPHeaderField:@"Cookie"]; [self.webView loadRequest:request];
- (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{ NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage]; NSMutableString * cookieString = [[NSMutableString alloc]init]; for (NSHTTPCookie*cookie in [cookieJar cookies]) { [cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value]; }
|
但是这只能解决第一次进入的cookie问题,如果页面内跳转(a标签等)还是取不到cookie,因此还要再加代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
十一、加载页面后自动关闭的问题
问题描述,我加载一web页面后,进行各种操作,比说我充值,什么的,然后想要在充值提出成功后自顶关闭这个web页面回到上一层或者返回到某一个界面,就用下面的方法,一般判断URL 包含的字符串都是后台给定的,在这里只需要判断就好了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
|
十二、清除缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
- (void)cleanCacheAndCookie {
|
1 2 3 4 5 6 7 8 9
|
- (void)dealloc { [_webView stopLoading]; [_webView setNavigationDelegate:nil]; [self clearCache]; [self cleanCacheAndCookie]; }
|
附:demo中使用的返回上一页和关闭浏览器的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
#pragma mark - Actions
- (void)backNative { //判断是否有上一层H5页面 if ([self.webView canGoBack]) { //如果有则返回 [self.webView goBack]; //同时设置返回按钮和关闭按钮为导航栏左边的按钮 // self.navigationItem.leftBarButtonItems = @[self.backBtn, self.closeBtn]; }else { [self closeNative]; } }
- (void)closeNative { if (self.webView.backForwardList.backList.count>0) { //得到栈里面的list NSLog(@"backList==%@",self.webView.backForwardList.backList); NSLog(@"currentItem==%@",self.webView.backForwardList.currentItem); [self.webView goToBackForwardListItem:[self.webView.backForwardList.backList firstObject]];
// WKBackForwardListItem * item = self.webView.backForwardList.currentItem; //得到现在加载的list // for (WKBackForwardListItem * backItem in self.webView.backForwardList.backList) { //循环遍历,得到你想退出到 // //添加判断条件 // [self.webView goToBackForwardListItem:[self.webView.backForwardList.backList firstObject]]; // } } [self.navigationController popViewControllerAnimated:YES]; }
|