iOS開發-WKWebView的使用

前言

一直以來項目中都習慣使用UIWebview,其實WK功能更增強大,包括與JS的交互。WK的加載速度比UIWebView提高差很少一倍,內存使用上面反而還少了一半。而且蘋果推薦使用WK。因此,今天就整理一下WK的相關知識。html

簡介

iOS8.0以後,蘋果推薦使用WebKit框架中的WKWebView來加載網頁,使用WKWebViewConfiguration來配置JS交互。java

使用

首先導入#import <WebKit/WebKit.h> 建立UIgit

_webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_W, SCREEN_H) configuration:config];  //config爲建立好的配置對象
    _webView.UIDelegate = self;        // UI代理
    _webView.navigationDelegate = self;        // 導航代理
    _webView.allowsBackForwardNavigationGestures = YES;       // 左滑返回
 
   //可返回的頁面列表, 存儲已打開過的網頁 
    WKBackForwardList * backForwardList = [_webView backForwardList];

複製代碼

經常使用方法

打開網頁github

NSString *path = [[NSBundle mainBundle] pathForResource:@"JStoOC.html" ofType:nil];
    NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
      //加載本地html文件
    [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
複製代碼

網頁切換web

[_webView goBack];    //頁面後退

    [_webView goForward];    //頁面前進

    [_webView reload];    //刷新當前頁面
複製代碼

網頁執行JS方法緩存

[_webView evaluateJavaScript:@"h5method()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
                              
}];
複製代碼

OC與JS的交互

  • js調用OC

主要依靠WKScriptMessageHandler協議類、WKUserContentController其中: WKUserContentController對象負責註冊JS方法,設置處理接收JS方法的代理,代理遵照WKScriptMessageHandler,實現捕捉到JS消息的回調方法。服務器

一、配置與JS的交互 用WKWebViewConfiguration來配置JS交互markdown

//一、建立網頁配置對象
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        
    // 二、添加設置對象WKPreferences
    WKPreferences *preference = [[WKPreferences alloc]init];
    //2.1最小字體大小
    // 當將javaScriptEnabled屬性設置爲NO時,能夠看到明顯的效果
    preference.minimumFontSize = 0;
    //2.2是否支持javaScript 默認是支持的
    preference.javaScriptEnabled = YES;  
    //2.3是否容許不通過用戶交互由javaScript自動打開窗口
    preference.javaScriptCanOpenWindowsAutomatically = YES;   // 默認爲NO
    // 2.4添加  
    config.preferences = preference;
        
    //三、通常配置
    // 是使用h5的視頻播放器在線播放, 仍是使用原生播放器全屏播放
    config.allowsInlineMediaPlayback = YES;
    //設置視頻是否須要用戶手動播放,設置爲NO則會容許自動播放
    config.requiresUserActionForMediaPlayback = YES;
    //設置是否容許畫中畫技術 (在特定設備上有效
    config.allowsPictureInPictureMediaPlayback = YES;
    //設置請求的User-Agent信息中應用程序名稱( iOS9後可用
    config.applicationNameForUserAgent = @"ChinaDailyForiPad";

複製代碼

二、使用WKUserContentController,用來作 原生與JavaScript的交互管理cookie

WKUserContentController * wkUController = [[WKUserContentController alloc] init];    
    //註冊一個name爲 jsToOcNoPrams 的js方法
    [wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcNoPrams"];
    [wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcWithPrams"]; 

    // 設置 
    config.userContentController = wkUController;

    // 用完移除
    [[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcNoPrams"];
       [[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcWithPrams"];
複製代碼

三、使用協議類WKScriptMessageHandler,用來處理監聽JavaScript方法從而調用原生OC方法。(和WKUserContentController搭配使用)app

// 建立代理
    WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate =
 [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];

複製代碼

四、經過 接收JS傳出消息的name 進行捕捉的回調方法 ps:遵照WKScriptMessageHandler協議,代理是由WKUserContentControl設置

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{

    NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
    
    //用message.body得到JS傳出的參數體
    NSDictionary * parameter = message.body;

    //JS調用OC
    if([message.name isEqualToString:@"jsToOcNoPrams"]){
        
        NSLog("----->js調用到了oc , 不帶參數");      
  
    }else if([message.name isEqualToString:@"jsToOcWithPrams"]){
         NSLog("----->js調用到了oc , 帶參數");      
    }
}
複製代碼
  • OC調用js

使用WKUserScript,執行自定義的JavaScript代碼

// js代碼字符串
    NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    // 建立WKUserScript
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    //  設置
    [config.userContentController addUserScript:wkUScript];
複製代碼

動態加載並運行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];

NSString *html = @"<head></head><imgea src='http://www.nsu.edu.cn/v/2014v3/img/background/3.jpg' />";
[_webView loadHTMLString:html baseURL:nil];
[self.view addSubview:_webView];
複製代碼

WKWebView涉及的代理方法

一、WKNavigationDelegate協議 主要處理一些跳轉、加載處理操做

  • 頁面開始加載時調用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {

}
複製代碼
  • 頁面加載失敗時調用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {

    [self.progressView setProgress:0.0f animated:NO];
}
複製代碼
  • 當內容開始返回時調用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
}
複製代碼
  • 頁面加載完成以後調用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    // [self getCookie];
}
複製代碼
  • 提交發生錯誤時調用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {

    [self.progressView setProgress:0.0f animated:NO];
}
複製代碼
  • 接收到服務器跳轉請求,即服務重定向時以後調用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
}
複製代碼
  • 根據WebView對於即將跳轉的HTTP請求頭信息和相關信息

來決定是否跳轉

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    
    NSString * urlStr = navigationAction.request.URL.absoluteString;

    NSString *htmlHeadString = @"github://";    //本身定義的協議頭

    if([urlStr hasPrefix:htmlHeadString]){
    // 經過截取URL調用OC
    NSURL * url = [NSURL URLWithString:[urlStr stringByReplacingOccurrencesOfString:@"github://callName_?" withString:@""]];
            [[UIApplication sharedApplication] openURL:url];

        decisionHandler(WKNavigationActionPolicyCancel);  // 
    }else{
        decisionHandler(WKNavigationActionPolicyAllow);   // 容許直接跳轉
    }
}
複製代碼
  • 根據客戶端受到的服務器響應頭,以及response相關信息,來決定是否能夠跳轉
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSString * urlStr = navigationResponse.response.URL.absoluteString;
    NSLog(@"當前跳轉地址:%@",urlStr);
    //容許跳轉
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不容許跳轉
    //decisionHandler(WKNavigationResponsePolicyCancel);
}
複製代碼
  • 須要響應身份驗證時調用 一樣在block中須要傳入用戶身份憑證
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    //用戶身份信息
    NSURLCredential * newCred = [[NSURLCredential alloc] initWithUser:@"user123" password:@"123" persistence:NSURLCredentialPersistenceNone];

    //爲 challenge 的發送方提供 credential
    [challenge.sender useCredential:newCred forAuthenticationChallenge:challenge];
    completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}
複製代碼

2.WKUIDelegate協議 主要處理JS腳本,確認框,警告框等

  • web界面中有彈出警告框時調用
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {

     NSLog("HTML彈出框");
    completionHandler();
}
複製代碼

@param webView : 實現該代理的webview

@param message          : 警告框中的內容
 @param completionHandler : 警告框消失回調
複製代碼
  • 確認框。JavaScript調用confirm方法後回調的方法

confirm是js中的肯定框,須要在block中把用戶選擇的狀況傳遞進去

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    completionHandler(NO);
}
複製代碼
  • 輸入框。JavaScript調用prompt方法後回調的方法

prompt是js中的輸入框,須要在block中把用戶輸入的信息傳入

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = defaultText;
    }];
    [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}
複製代碼
  • 頁面是彈出窗口 _blank 處理
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}
複製代碼

網頁內容加載進度條

0.添加進度條

// 屬性
@property (nonatomic, strong) UIProgressView *progressView;

// 懶加載
- (UIProgressView *)progressView
{
    if (!_progressView)
    {
        _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_W, 2)];
        _progressView.backgroundColor = [UIColor blueColor];
        _progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
        _progressView.progressTintColor = [UIColor app_color_yellow_eab201];
        [self.view addSubview:self.progressView];
    }
    return _progressView;
}
複製代碼

1.添加觀察者

//添加 監測網頁加載進度 的觀察者
    [self.webView addObserver:self
                   forKeyPath:@"estimatedProgress"
                      options:0
                      context:nil];

//添加 監測網頁標題title 的觀察者
    [self.webView addObserver:self
                   forKeyPath:@"title"
                      options:NSKeyValueObservingOptionNew
                      context:nil];
複製代碼

2.監聽方法

//---kvo 監聽進度 必須實現此方法
-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                      context:(void *)context
{
    if ([keyPath isEqualToString:@"estimatedProgress"])
    {
       self.progressView.progress = self.webView.estimatedProgress;
       if (self.progressView.progress == 1)
       {
           WeakSelfDeclare
           [UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^
           {
               weakSelf.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.4f);
           }
                            completion:^(BOOL finished)
           {
               weakSelf.progressView.hidden = YES;
           }];
       }
   }else if([keyPath isEqualToString:@"title"]
             && object == _webView){
        self.navigationItem.title = _webView.title;
    }else{
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

複製代碼

3.顯示/隱藏進度條

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
       self.progressView.hidden = NO;
       self.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
       [self.view bringSubviewToFront:self.progressView];
}

 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
       self.progressView.hidden = YES;
}
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
       if(error.code==NSURLErrorCancelled)
       {
           [self webView:webView didFinishNavigation:navigation];
       }
       else
       {
           self.progressView.hidden = YES;
       }
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
       self.progressView.hidden = YES;
       [self.navigationItem setTitleWithCustomLabel:@"加載失敗"];
}

複製代碼

4.移除觀察者

[_webView removeObserver:self
                  forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
    [_webView removeObserver:self
                  forKeyPath:NSStringFromSelector(@selector(title))];
複製代碼

清除WK緩存

- (void)cleanCacheAndCookie
{
    //清除cookies
    NSHTTPCookie *cookie;
    NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (cookie in [storage cookies])
    {
        [storage deleteCookie:cookie];
    }
    
    [[NSURLCache sharedURLCache] removeAllCachedResponses];
    NSURLCache * cache = [NSURLCache sharedURLCache];
    [cache removeAllCachedResponses];
    [cache setDiskCapacity:0];
    [cache setMemoryCapacity:0];
    
    WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
    [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                     completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records)
     {
         for (WKWebsiteDataRecord *record  in records)
         {
             
             [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
                                                       forDataRecords:@[record]
                                                    completionHandler:^
              {
                  NSLog(@"Cookies for %@ deleted successfully",record.displayName);
              }];
         }
     }];
}
複製代碼
- (void)dealloc
{
    [_webView stopLoading];
    [_webView setNavigationDelegate:nil];
    [self clearCache];
    [self cleanCacheAndCookie];
}
複製代碼

點擊連接無反應

#pragma mark WKNavigationDelegate

-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
    
}
複製代碼

WKWebView計算內容高度

添加KVO

[_webView.scrollView addObserver:selfforKeyPath:@"contentSize"options:NSKeyValueObservingOptionNewcontext:nil];


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if ([keyPath isEqualToString:@"contentSize"]) {
        dispatch_async(dispatch_get_global_queue(0,0), ^{

            //document.documentElement.scrollHeight
            //document.body.offsetHeight
            [_webView evaluateJavaScript:@"document.documentElement.offsetHeight"completionHandler:^(id_Nullable result, NSError * _Nullable error) {

                CGRect frame =_webView.frame;
                frame.size.height = [result doubleValue] + 50;
                _webView.frame = frame;
                _scrollViewHeight =220 + _webView.height;
                _scrollView.contentSize =CGSizeMake(kScreenWidth,_scrollViewHeight);
            }];
        });
    }
}
複製代碼

H5調用了撥打電話功能

先攔截特色scheme,而後執行撥打電話的代碼

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // 攔截
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"tel"]) {
        NSString *resourceSpecifier = [URL resourceSpecifier];
        NSString *callPhone = [NSString stringWithFormat:@"telprompt://%@", resourceSpecifier];
        
        decisionHandler(WKNavigationActionPolicyCancel);
        // 撥打
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callPhone]];
        return ;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}
複製代碼

參考資料

www.jianshu.com/p/20cfd4f8c… www.jianshu.com/p/5cf0d241a… www.jianshu.com/p/6ba250744… * www.jianshu.com/p/4d12d593b…

相關文章
相關標籤/搜索