系列文章:
- 《37- WKWebView項(xiàng)目實(shí)踐分享(一)- UIWebView回顧介紹》
- 《43- WKWebView項(xiàng)目實(shí)踐分享(二)- WKWebView介紹》
- 《38- WKWebView項(xiàng)目實(shí)踐分享(三)- native和webView交互》
- 《39- WKWebView項(xiàng)目實(shí)踐分享(四) - 先了解下Cookie》
- 《40- WKWebView項(xiàng)目實(shí)踐分享(五)- WKWebView如何加Cookie》
- 《42- WKWebView項(xiàng)目實(shí)踐分享(七) - 補(bǔ)充: 實(shí)踐中的坑》
設(shè)置User Agent
User Agent百度百科釋義是。中文名為用戶代理木人,簡(jiǎn)稱 UA信柿,它是一個(gè)特殊字符串頭,使得服務(wù)器能夠識(shí)別客戶使用的操作系統(tǒng)及版本醒第、CPU 類型渔嚷、瀏覽器及版本、瀏覽器渲染引擎稠曼、瀏覽器語(yǔ)言形病、瀏覽器插件等。
簡(jiǎn)單理解就是一句話:讓服務(wù)器知道C端設(shè)備的信息,白話點(diǎn)就是漠吻, 你得讓H5后臺(tái)知道打開(kāi)這個(gè)網(wǎng)頁(yè)是從你們公司app上打開(kāi)的量瓜,這對(duì)大數(shù)據(jù)和廣告統(tǒng)計(jì)非常關(guān)鍵。
設(shè)置方式兩種途乃,一種是iOS8.0開(kāi)始都使用NSUserDefaults绍傲。另一種是從iOS9.0開(kāi)始,使用WKWebView提供的API:customUserAgent耍共。
if ([UIDevice currentDevice].systemVersion.floatValue >= 9.0)
{
NSString *newUserAgent = @"YCWebKit";
self.wkWebView.customUserAgent = newUserAgent;
}
if ([UIDevice currentDevice].systemVersion.floatValue < 9.0)
{
NSString *newUserAgent = @"YCWebKit";
[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":newUserAgent}];
[[NSUserDefaults standardUserDefaults] synchronize];
}
注意:
1. NSUserDefaults這種方式一定要在初始化WKWebView之前設(shè)置才有效
**2. 后期項(xiàng)目使用中的過(guò)程中烫饼,遇到了一個(gè)關(guān)于User Agent的坑, 特別注意试读, 設(shè)置的時(shí)候不要覆蓋手機(jī)原生User Agent杠纵, 我們要把我們自己公司的自定義User Agent字段追加到原生后邊可以。否則會(huì)發(fā)生一些意想不到的錯(cuò)誤钩骇。
具體看《42- WKWebView(6) - 補(bǔ)充: 實(shí)踐中的坑》
**
跨域問(wèn)題
跨域分成兩種:
- 一個(gè)是在相同請(qǐng)求協(xié)議下比藻,host不同。比如說(shuō)伊履,在
http://www.a.com/
點(diǎn)擊一個(gè)按鈕跳轉(zhuǎn)到了http://www.b.com/
頁(yè)面是這個(gè)就叫做跨域韩容。 - 直接請(qǐng)求協(xié)議就不同款违,這也是跨域唐瀑。比如說(shuō):
http://www.a.com/
到https://www.a.com/
跨域?qū)kWebView有什么影響呢?基于上一篇Cookie的方案插爹,經(jīng)過(guò)實(shí)踐發(fā)現(xiàn)哄辣,在iOS11.0以下,WKWebView中HTTPS 對(duì) HTTPS赠尾、HTTP 對(duì) HTTP 的跨域是能載入的力穗。但是沒(méi)辦法跨域用document.cookie設(shè)置cookie,也就是前一頁(yè)面document.cookie中的cookie帶不過(guò)去气嫁。 在iOS11.0以上当窗,使用WKHttpCookieStore,從b.com頁(yè)面執(zhí)行g(shù)oBack()方法返回到上一頁(yè)a.com時(shí)寸宵,a.com的request Header中額外添加設(shè)置的appver和devised兩個(gè)屬性丟失崖面,但是Cookie還在。
跨域問(wèn)題的出現(xiàn)是因?yàn)閃ebKit框架對(duì)跨域進(jìn)行了安全性檢查限制梯影,不允許跨域巫员。那么怎么解決呢?我一共試驗(yàn)了兩種解決方案甲棍。
第一種方案:修復(fù)
在請(qǐng)求過(guò)程中request是readOnly的简识,也就是我們沒(méi)辦法在請(qǐng)求過(guò)程中把丟失的屬性在HTTPHeader中加上,繼續(xù)請(qǐng)求。 所以只能是攔截到具體URL七扰,然后重新賦值Cookie和其它參數(shù)奢赂,執(zhí)行l(wèi)oadRequest。 但是這樣在我們現(xiàn)兩個(gè)問(wèn)題行的導(dǎo)航條需求下戳寸,會(huì)出現(xiàn)兩個(gè)問(wèn)題:
- webView.backForwardList.backList始終不會(huì)為空呈驶,導(dǎo)致如果點(diǎn)擊返回退出控制器,需要
手動(dòng)加邏輯處理疫鹊。 - 當(dāng)在a.com/下不跳轉(zhuǎn)的情況下袖瞻,對(duì)頁(yè)面進(jìn)行操作,界面變更之后拆吆。進(jìn)入b.com/然后使用此
方法重新loadRequest鏈接a.com/聋迎,頁(yè)面恢復(fù)初始化,之前操作丟失枣耀。
第二種方案:新打開(kāi)一個(gè)webView控制器
這種方案的顧慮如果a.com/要從b.com/頁(yè)面中獲取返回?cái)?shù)據(jù)霉晕,會(huì)導(dǎo)致無(wú)法拿到數(shù)據(jù)。但是從公司H5開(kāi)發(fā)小哥那里分享到經(jīng)驗(yàn)捞奕,一般H5不會(huì)這么做牺堰,取數(shù)據(jù)只是在同一個(gè)頁(yè)面中。那這樣就很簡(jiǎn)單了颅围。
和后臺(tái)約定在http://b.com
鏈接后邊加上自定義標(biāo)識(shí)伟葫,比如說(shuō)OPenNewVC=1
。那么此時(shí)我們?cè)?code>a.com/中點(diǎn)擊某個(gè)按鈕觸發(fā)的跳轉(zhuǎn)鏈接就是http://b.com?OPenNewVC=1
院促,然后在decidePolicyForNavigationAction
方法中攔截筏养,然后打開(kāi)新控制器。
設(shè)置重定向
在WKWebView中常拓,網(wǎng)頁(yè)如果有重定向的行為渐溶,會(huì)直接回調(diào)didReceiveServerRedirectForProvisionalNavigation
。但是在實(shí)際測(cè)試中發(fā)現(xiàn)弄抬,有的網(wǎng)頁(yè)雖然進(jìn)入了這個(gè)方法茎辐,但是不需要我們手動(dòng)干預(yù),就可以重新跳轉(zhuǎn)到重定向后的頁(yè)面掂恕,你手動(dòng)干預(yù)了反而導(dǎo)致請(qǐng)求不成功拖陆。但是有的網(wǎng)頁(yè),就需要自己重新loadRequest一下才可以竹海。
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
if (webView.isLoading
&& ![webView.URL.absoluteString containsString:@".a.com"]
) {
[self loadRequestURL:webView.URL.absoluteString];
}
}
白屏問(wèn)題
在《騰訊Bugly: WKWebView 那些坑》的關(guān)于白屏問(wèn)題的描述是這樣的:
"WKWebView 自詡擁有更快的加載速度慕蔚,更低的內(nèi)存占用,但實(shí)際上 WKWebView 是一個(gè)多進(jìn)程組件斋配,
Network Loading 以及 UI Rendering 在其它進(jìn)程中執(zhí)行孔飒。初次適配 WKWebView 的時(shí)候灌闺,我們也驚
訝于打開(kāi) WKWebView 后,App 進(jìn)程內(nèi)存消耗反而大幅下降坏瞄,但是仔細(xì)觀察會(huì)發(fā)現(xiàn)桂对,Other Process
的內(nèi)存占用會(huì)增加。在一些用 webGL 渲染的復(fù)雜頁(yè)面鸠匀,使用 WKWebView 總體的內(nèi)存占用(App Pr
ocess Memory + Other Process Memory)不見(jiàn)得比 UIWebView 少很多蕉斜。
在 UIWebView 上當(dāng)內(nèi)存占用太大的時(shí)候,App Process 會(huì) crash缀棍;而在 WKWebView 上當(dāng)總體的內(nèi)
存占用比較大的時(shí)候宅此,WebContent Process 會(huì) crash,從而出現(xiàn)白屏現(xiàn)象爬范。"
總之父腕,就是因?yàn)槟撤N原因,Web Content Process奔潰了青瀑,從而出現(xiàn)白屏現(xiàn)象璧亮。
因?yàn)槲业捻?xiàng)目里,暫時(shí)沒(méi)有遇到這個(gè)問(wèn)題斥难。所以大家可以先看一下騰訊的解決方案枝嘶。
此處等待驗(yàn)證: 需要注意的一點(diǎn)是,我在之前的測(cè)試中哑诊,發(fā)現(xiàn)貌似上邊提到的重定向失敗也會(huì)進(jìn)入這個(gè)方法群扶。但是
處理a標(biāo)簽和_blank
需要通過(guò)navigationAction.targetFrame判斷目標(biāo)frame是不是主frame,如果不是主frame搭儒,那么就說(shuō)明是新開(kāi)一個(gè)tab操作穷当。
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
WKFrameInfo *frameInfo = navigationAction.targetFrame;
if(frameInfo == nil || frameInfo.isMainFrame == NO){
[webView loadRequest:[YCWebViewCookieTool fixRequest:navigationAction.request]];
}
return nil;
}
處理Alert彈框
WKWebView把WebView調(diào)用native彈框的處理也交給我們提茁,我們可以根據(jù)自己的需要進(jìn)行定制淹禾。注意alertView點(diǎn)擊之后需要調(diào)用一下代理方法中的block。
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"溫馨提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}];
[alert addAction:action1];
[self presentViewController:alert animated:YES completion:NULL];
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"溫馨提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"刪除" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}];
UIAlertAction *action2 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}];
[alert addAction:action1];
[alert addAction:action2];
[self presentViewController:alert animated:YES completion:NULL];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:defaultText message:@"JS調(diào)用輸入框" preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.textColor = [UIColor redColor];
}];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler([[alert.textFields lastObject] text]);
}]];
[self presentViewController:alert animated:YES completion:NULL];
}
Https請(qǐng)求的證書(shū)驗(yàn)證
WKWebView中提供了didReceiveAuthenticationChallenge:
方法來(lái)判斷茴扁。我們可以彈個(gè)alert讓用戶選擇是否信任铃岔,也可以默認(rèn)直接設(shè)置信任。 以下的處理方式朋友分享的一個(gè)峭火,源頭可能來(lái)自《wkwebview下的https請(qǐng)求》:
/**
https 請(qǐng)求會(huì)進(jìn)這個(gè)方法毁习,在里面進(jìn)行https證書(shū)校驗(yàn)、白名單域名判斷等操作
*/
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
/*
NSURLSessionAuthChallengeUseCredential = 0, 使用證書(shū)
NSURLSessionAuthChallengePerformDefaultHandling = 1, 忽略證書(shū)(默認(rèn)的處理方式)
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 忽略書(shū)證, 并取消這次請(qǐng)求
NSURLSessionAuthChallengeRejectProtectionSpace = 3, 拒絕當(dāng)前這一次, 下一次再詢問(wèn)
*/
NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
// 判斷服務(wù)器返回的證書(shū)類型, 是否是服務(wù)器信任
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef secTrustRef = challenge.protectionSpace.serverTrust;
if (secTrustRef != NULL) {// 信任是否為空
SecTrustResultType result;
OSErr er = SecTrustEvaluate(secTrustRef, &result);
if (er != noErr) {// 是否有錯(cuò)誤信息
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace,nil);
return;
}else{// 沒(méi)有錯(cuò)誤信息
if (result == kSecTrustResultRecoverableTrustFailure) {// 證書(shū)不受信任
CFArrayRef secTrustProperties = SecTrustCopyProperties(secTrustRef);
NSArray *arr = CFBridgingRelease(secTrustProperties);
NSMutableString *errorStr = [NSMutableString string];
for (int i=0;i<arr.count;i++){
NSDictionary *dic = [arr objectAtIndex:i];
if (i != 0 ) {
[errorStr appendString:@" "];
}
[errorStr appendString:(NSString*)dic[@"value"]];
}
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(secTrustRef, 0);
CFStringRef cfCertSummaryRef = SecCertificateCopySubjectSummary(certRef);
NSString *certSummary = (NSString *)CFBridgingRelease(cfCertSummaryRef);
NSString *title = @"該服務(wù)器無(wú)法驗(yàn)證";
NSString *message = [NSString stringWithFormat:@" 是否通過(guò)來(lái)自%@標(biāo)識(shí)為 %@證書(shū)為%@的驗(yàn)證. \n%@" , @"我的app",webView.URL.host,certSummary, errorStr];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
NSURLCredential* credential = [NSURLCredential credentialForTrust:secTrustRef];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}]];
// 彈出權(quán)限提示框
[self presentViewController:alertController animated:YES completion:^{}];
return;
}else{// 證書(shū)受信任
NSURLCredential* credential = [NSURLCredential credentialForTrust:secTrustRef];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
return;
}
}
}else{//信任不為空
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
}else{//非服務(wù)器信任
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
關(guān)于在線播放視頻
需要注意mediaTypesRequiringUserActionForPlayback
這個(gè)屬性設(shè)置哪些媒體資源需要用戶手動(dòng)操作一下才能播放卖丸,也就是否自動(dòng)播放纺且。WKAudiovisualMediaTypeNone
代表視頻和音頻資料都自動(dòng)播放。
WKWebViewConfiguration * webConfiguration = [[WKWebViewConfiguration alloc]init];
WKUserContentController *contentController = [[WKUserContentController alloc] init];
// 是否允許HTML5頁(yè)面在線播放視頻稍浆,否則使用native播放器
webConfiguration.allowsInlineMediaPlayback = YES;
// 是指不需要用戶操作载碌,進(jìn)入webView頁(yè)面視頻自動(dòng)播放
if (YCSystemVersionValue > 10.0) {
webConfiguration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
}
else if (9.0 < YCSystemVersionValue && YCSystemVersionValue < 10.0) {
webConfiguration.requiresUserActionForMediaPlayback = NO;
}
else if (8.0 < YCSystemVersionValue && YCSystemVersionValue< 9.0) {
webConfiguration.mediaPlaybackRequiresUserAction = NO;
}
關(guān)于selectionGranularity屬性
selectionGranularity這個(gè)屬性是設(shè)置了用戶拷貝網(wǎng)頁(yè)內(nèi)容的時(shí)候的粒度猜嘱。粒度可能很不好理解。我們直接找個(gè)新聞網(wǎng)頁(yè)看下設(shè)置之后的效果嫁艇。當(dāng)設(shè)置為WKSelectionGranularityCharacter, 在iOS9上復(fù)制文本沒(méi)有定位光標(biāo)朗伶。
具體可以看下:
屏幕旋轉(zhuǎn)
// 屏幕旋轉(zhuǎn)
wkWebView.translatesAutoresizingMaskIntoConstraints = NO;
wkWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
NSURLProtocol
WKWebView中使用NSURLProtocol需要使用私有API论皆,而且用了之后有兩個(gè)問(wèn)題。網(wǎng)上的一些方案可以過(guò)審猾漫,但是考慮到我們項(xiàng)目并非必要這個(gè)需求和使用之后的不確定性以及工作量点晴。最終放棄NSURLProtocol。不過(guò)先期也了解了一下悯周,提供大家?guī)灼诲e(cuò)的文章參考:
參考
強(qiáng)烈建議你把下邊的參考文章也快速看下觉鼻,作為拓展和補(bǔ)充:
- 《1. WKWebView 的使用》
- 《ObjC WKWebView精講》
- 《WKWebView從入門到趟坑》
- 《WKWebView》
- 《iOS11.3 WKWebView清除cookie所踩的坑!》
- 《WKWebView 的使用和踩過(guò)的坑》
- 《WKWebViewTips》
交流
希望能和大家交流技術(shù)
Blog:http://www.lilongcnc.cc