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]; }
|