UIWebView -> WKWebView

非商業(yè)行為,為自己復(fù)習(xí)鞏固

IOS 8之前使用UIWebView,IOS 8之后就出現(xiàn)了WKWebView,
相比UIWebView,WKWebView優(yōu)化了較多.

  1. WKWebView的內(nèi)存開(kāi)銷(xiāo)比UIWebView小很多
  2. 內(nèi)置手勢(shì)
  3. 支持了更多的HTML5特性
  4. 有Safari相同的JavaScript引擎
  5. 提供常用的屬性如加載網(wǎng)頁(yè)進(jìn)度的estimatedProgress屬性
UIWebView和WKWebView的流程.png

WKWebView的流程粒度更加細(xì)致

#請(qǐng)求數(shù)據(jù)的時(shí)候詢(xún)問(wèn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
#返回?cái)?shù)據(jù)的時(shí)候詢(xún)問(wèn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

流程中,WKWebView返回的錯(cuò)誤

#請(qǐng)求數(shù)據(jù)時(shí)發(fā)生的error
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
#請(qǐng)求之后加載H5發(fā)生的error
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;

WKWebView基本使用

使用WKWebView引用頭文件

- (void)setupWebview{
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.selectionGranularity = WKSelectionGranularityDynamic;
    config.allowsInlineMediaPlayback = YES;
    WKPreferences *preferences = [WKPreferences new];
    //是否支持JavaScript
    preferences.javaScriptEnabled = YES;
    //不通過(guò)用戶(hù)交互,是否可以打開(kāi)窗口
    preferences.javaScriptCanOpenWindowsAutomatically = YES;
    config.preferences = preferences;
    WKWebView *webview = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight - 64) configuration:config];
    [self.view addSubview:webview];
     
    /* 加載服務(wù)器url的方法*/
    NSString *url = @"https://www.baidu.com";
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    [webview loadRequest:request];
     
    webview.navigationDelegate = self;
    webview.UIDelegate = self;
}

WKNavigationDelegate

#pragma mark - WKNavigationDelegate
/* 頁(yè)面開(kāi)始加載 */
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
}
/* 開(kāi)始返回內(nèi)容 */
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
     
}
/* 頁(yè)面加載完成 */
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
     
}
/* 頁(yè)面加載失敗 */
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
     
}
/* 在發(fā)送請(qǐng)求之前,決定是否跳轉(zhuǎn) */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    //允許跳轉(zhuǎn)
    decisionHandler(WKNavigationActionPolicyAllow);
    //不允許跳轉(zhuǎn)
    //decisionHandler(WKNavigationActionPolicyCancel);
}
/* 在收到響應(yīng)后,決定是否跳轉(zhuǎn) */
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
     
    NSLog(@"%@",navigationResponse.response.URL.absoluteString);
    //允許跳轉(zhuǎn)
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允許跳轉(zhuǎn)
    //decisionHandler(WKNavigationResponsePolicyCancel);
}

WKWebView細(xì)節(jié)

url 中文處理

加載的url中出現(xiàn)了中文需要手動(dòng)轉(zhuǎn)碼,但同時(shí)又要保證URL中的特殊字符保持不變,那么我們可以使用下面的方法(方法)

- (NSURL *)url{
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
    return [NSURL URLWithString:(NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, 
                (CFStringRef)self, (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", 
                NULL,kCFStringEncodingUTF8))];
#pragma clang diagnostic pop
}

獲取h5中的標(biāo)題以及添加進(jìn)度條

獲取h5中的標(biāo)題和添加進(jìn)度條 在初始化webView添加兩個(gè)觀察者分別用來(lái)監(jiān)聽(tīng)webView的estimateProgress和title屬性

webview.navigationDelegate = self;
webview.UIDelegate = self;
     
[webview addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
[webview addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];

添加創(chuàng)建進(jìn)度條,并添加進(jìn)度條圖層屬性:

@property (nonatomic,weak) CALayer *progressLayer;

-(void)setupProgress{
    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;
}

觀察者的回調(diào)方法:

#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"];
    }
}</nskeyvaluechangekey,id>

添加userAgent信息

h5的歐版需要我們?yōu)閃ebView的請(qǐng)求添加userAgent,來(lái)識(shí)別操作系統(tǒng)等信息,但如果每次用到webView都要添加一次會(huì)比較麻煩,下面是一種解決問(wèn)題的辦法

在Appdelegate中添加一個(gè)WKWebView的屬性,啟動(dòng)app時(shí)直接為該屬性添加userAgent:

- (void)setUserAgent {
    _webView = [[WKWebView alloc] initWithFrame:CGRectZero];
    [_webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
        if (error) { return; }
        NSString *userAgent = result;
        if (![userAgent containsString:@"/mobile-iOS"]) {
            userAgent = [userAgent stringByAppendingString:@"/mobile-iOS"];
            NSDictionary *dict = @{@"UserAgent": userAgent};
            [TKUserDefaults registerDefaults:dict];
        }
    }];
}

JS調(diào)用OC

js通過(guò)以下方法調(diào)用原生方法

window.webkit.messageHandlers.<#對(duì)象名#>.postMessage(<#參數(shù)#>)

原生中要實(shí)現(xiàn)WKScriptMessageHandler的代理方法,值得注意的是參數(shù)name需要與上述代碼中對(duì)象名一致。

// 添加scriptMessageHandler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler 
         name:(NSString *)name;

然后在

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

在方法中獲取做判斷,響應(yīng)對(duì)應(yīng)的方法:

// 初始化WKWebView,在實(shí)例化WKWebViewConfiguration對(duì)象的時(shí)候我們同時(shí)添加scriptMessageHandler
 //進(jìn)行配置控制器
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
//實(shí)例化對(duì)象
configuration.userContentController = [WKUserContentController new];
//調(diào)用JS方法
[configuration.userContentController addScriptMessageHandler:self name:@"btnClick"];

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"btnClick"]) {
        NSDictionary *jsData = message.body;
        NSLog(@"%@", message.name, jsData);
        //讀取js function的字符串
        NSString *jsFunctionString = jsData[@"result"];
        //拼接調(diào)用該方法的js字符串(convertDictionaryToJson:方法將NSDictionary轉(zhuǎn)成JSON格式的字符串)
        NSString *jsonString = [NSDictionary convertDictionaryToJson:@{@"test":@"123", @"data":@"666"}];
        NSString *jsCallBack = [NSString stringWithFormat:@"(%@)(%@);", jsFunctionString, jsonString];
        //執(zhí)行回調(diào)
        [self.weWebView evaluateJavaScript:jsCallBack completionHandler:^(id _Nullable result, NSError * _Nullable error) {
            if (error) {
                NSLog(@"err is %@", error.domain);
            }
        }];
    }
}

注意,message的body只能是NSNumber,NSString,NSDate,NSArray,NSDictionary,NSNull這幾種類(lèi)型,
所以我們無(wú)法將js函數(shù)直接原生,在需要進(jìn)行回調(diào)的環(huán)境下我們將js回調(diào)函數(shù)轉(zhuǎn)為String后再傳給原生,再由原生獲取后進(jìn)行回調(diào)操作,實(shí)際上這是已經(jīng)進(jìn)行了動(dòng)態(tài)js注入。

body.png

OC調(diào)用JS

動(dòng)態(tài)注入js方法

- (void)evaluateJavaScript:(NSString *)javaScriptString 
     completionHandler:(void (^ _Nullable)(_Nullable id, 
          NSError * _Nullable error))completionHandler;

示例代碼

// 此處是設(shè)置需要調(diào)用的js方法以及將對(duì)應(yīng)的參數(shù)傳入,需要以字符串的形式
NSString *jsFounction = [NSString stringWithFormat:@"getAppConfig('%@')", APP_CHANNEL_ID];
// 調(diào)用API方法
 [self.weexWebView evaluateJavaScript:jsFounction 
    completionHandler:^(id object, NSError * _Nullable error) {
        NSLog(@"obj:%@---error:%@", object, error);
    }];

純文本網(wǎng)頁(yè)
UIWebView字體正常,替換成WKweb后字體很小解決辦法。

let js = "var meta = document.createElement(`meta`); meta.setAttribute(`name`, `viewport`); meta.setAttribute(`content`, `width=device-width`); document.getElementsByTagName(`head`)[0].appendChild(meta)"

let wkUserScript:WKUserScript = WKUserScript.init(source: js, injectionTime .atDocumentEnd, forMainFrameOnly: true)

let wkUC:WKUserContentController = WKUserContentController.init()
wkUC.addUserScript(wkUserScript)

let wkWebConfig = WKWebViewConfiguration.init()
wkWebConfig.userContentController = wkUC

let wkPreferences:WKPreferences = WKPreferences.init()
wkPreferences.minimumFontSize = 28
wkWebConfig.preferences = wkPreferences

_wkWebView = WKWebView.init(frame: **,  configuration: wkWebConfig)

WKUIDelegate

UIWebView會(huì)由網(wǎng)頁(yè)彈出alert, 替換成wk后不能彈出。解決辦法

WKUIDelegate是webView在user interface上的代理,
共有5個(gè)可選類(lèi)型的代理方法。為webView提供了原生的彈框,而不是JavaScript里的提示框。

雖然JavaScript的提示框可以做的跟原生一樣,但是對(duì)于ios開(kāi)發(fā)者來(lái)說(shuō),如果要更改提示框就不方便了。提供這個(gè)代理可讓ios端更加靈活的修改提示框的樣式。

js中有三種提示框:Alert,Confirm,prompt對(duì)應(yīng)該代理如下:

/*  警告 */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    [[[UIAlertView alloc] initWithTitle:@"警告框" message:message delegate:nil cancelButtonTitle:@"確認(rèn)" otherButtonTitles: nil] show];
    completionHandler();
}
///** 確認(rèn)框 */
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
    [[[UIAlertView alloc] initWithTitle:@"確認(rèn)框" message:message delegate:nil cancelButtonTitle:@"確認(rèn)" otherButtonTitles: nil] show];

    completionHandler(1);
}
/**  輸入框 */
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
    [[[UIAlertView alloc] initWithTitle:@"輸入框" message:prompt delegate:nil cancelButtonTitle:@"確認(rèn)" otherButtonTitles: nil] show];
 completionHandler(@"誰(shuí)!");
}

除了上面的三個(gè)方法還有兩個(gè)代理方法。

// 創(chuàng)建新的webView
// 可以指定配置對(duì)象、導(dǎo)航動(dòng)作對(duì)象、window特性。如果沒(méi)用實(shí)現(xiàn)這個(gè)方法,不會(huì)加載鏈接,如果返回的是原webview會(huì)崩潰。
- (nullable WKWebView *)webView:(WKWebView *)webView 
        createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration 
        forNavigationAction:(WKNavigationAction *)navigationAction 
        windowFeatures:(WKWindowFeatures *)windowFeatures;
// webview關(guān)閉時(shí)回調(diào)
- (void)webViewDidClose:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
- (void)webView:(WKWebView *)webView 
        decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
 WKFrameInfo *sFrame = navigationAction.sourceFrame;//navigationAction的出處
 WKFrameInfo *tFrame = navigationAction.targetFrame;//navigationAction的目標(biāo)
//只有當(dāng)  tFrame.mainFrame == NO;時(shí),表明這個(gè) WKNavigationAction 將會(huì)新開(kāi)一個(gè)頁(yè)面。
//才會(huì)調(diào)用- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
}

新開(kāi)一個(gè)webView。
如果我們只是顯示網(wǎng)頁(yè)就感覺(jué)這么做耗性能沒(méi)有必要。

- (WKWebView *)webView:(WKWebView *)webView 
        createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
  WKFrameInfo *frameInfo = navigationAction.targetFrame;
  if (![frameInfo isMainFrame]) {
      [webView loadRequest:navigationAction.request];
  }
    return nil;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容