系列文章:
設(shè)置User Agent
User Agent百度百科釋義是。中文名為用戶代理,簡稱 UA,它是一個特殊字符串頭,使得服務(wù)器能夠識別客戶使用的操作系統(tǒng)及版本、CPU 類型、瀏覽器及版本、瀏覽器渲染引擎、瀏覽器語言、瀏覽器插件等。
簡單理解就是一句話:讓服務(wù)器知道C端設(shè)備的信息,白話點就是, 你得讓H5后臺知道打開這個網(wǎng)頁是從你們公司app上打開的,這對大數(shù)據(jù)和廣告統(tǒng)計非常關(guān)鍵。

設(shè)置方式兩種,一種是iOS8.0開始都使用NSUserDefaults。另一種是從iOS9.0開始,使用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. 后期項目使用中的過程中,遇到了一個關(guān)于User Agent的坑, 特別注意, 設(shè)置的時候不要覆蓋手機原生User Agent, 我們要把我們自己公司的自定義User Agent字段追加到原生后邊可以。否則會發(fā)生一些意想不到的錯誤。
具體看《42- WKWebView(6) - 補充: 實踐中的坑》
**
跨域問題
跨域分成兩種:
- 一個是在相同請求協(xié)議下,host不同。比如說,在
http://www.a.com/點擊一個按鈕跳轉(zhuǎn)到了http://www.b.com/頁面是這個就叫做跨域。 - 直接請求協(xié)議就不同,這也是跨域。比如說:
http://www.a.com/到https://www.a.com/
跨域?qū)kWebView有什么影響呢?基于上一篇Cookie的方案,經(jīng)過實踐發(fā)現(xiàn),在iOS11.0以下,WKWebView中HTTPS 對 HTTPS、HTTP 對 HTTP 的跨域是能載入的。但是沒辦法跨域用document.cookie設(shè)置cookie,也就是前一頁面document.cookie中的cookie帶不過去。 在iOS11.0以上,使用WKHttpCookieStore,從b.com頁面執(zhí)行g(shù)oBack()方法返回到上一頁a.com時,a.com的request Header中額外添加設(shè)置的appver和devised兩個屬性丟失,但是Cookie還在。
跨域問題的出現(xiàn)是因為WebKit框架對跨域進行了安全性檢查限制,不允許跨域。那么怎么解決呢?我一共試驗了兩種解決方案。
第一種方案:修復(fù)
在請求過程中request是readOnly的,也就是我們沒辦法在請求過程中把丟失的屬性在HTTPHeader中加上,繼續(xù)請求。 所以只能是攔截到具體URL,然后重新賦值Cookie和其它參數(shù),執(zhí)行l(wèi)oadRequest。 但是這樣在我們現(xiàn)兩個問題行的導(dǎo)航條需求下,會出現(xiàn)兩個問題:
- webView.backForwardList.backList始終不會為空,導(dǎo)致如果點擊返回退出控制器,需要
手動加邏輯處理。 - 當(dāng)在a.com/下不跳轉(zhuǎn)的情況下,對頁面進行操作,界面變更之后。進入b.com/然后使用此
方法重新loadRequest鏈接a.com/,頁面恢復(fù)初始化,之前操作丟失。
第二種方案:新打開一個webView控制器
這種方案的顧慮如果a.com/要從b.com/頁面中獲取返回數(shù)據(jù),會導(dǎo)致無法拿到數(shù)據(jù)。但是從公司H5開發(fā)小哥那里分享到經(jīng)驗,一般H5不會這么做,取數(shù)據(jù)只是在同一個頁面中。那這樣就很簡單了。
和后臺約定在http://b.com鏈接后邊加上自定義標(biāo)識,比如說OPenNewVC=1。那么此時我們在a.com/中點擊某個按鈕觸發(fā)的跳轉(zhuǎn)鏈接就是http://b.com?OPenNewVC=1,然后在decidePolicyForNavigationAction方法中攔截,然后打開新控制器。
設(shè)置重定向
在WKWebView中,網(wǎng)頁如果有重定向的行為,會直接回調(diào)didReceiveServerRedirectForProvisionalNavigation。但是在實際測試中發(fā)現(xiàn),有的網(wǎng)頁雖然進入了這個方法,但是不需要我們手動干預(yù),就可以重新跳轉(zhuǎn)到重定向后的頁面,你手動干預(yù)了反而導(dǎo)致請求不成功。但是有的網(wǎng)頁,就需要自己重新loadRequest一下才可以。
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
if (webView.isLoading
&& ![webView.URL.absoluteString containsString:@".a.com"]
) {
[self loadRequestURL:webView.URL.absoluteString];
}
}
白屏問題
在《騰訊Bugly: WKWebView 那些坑》的關(guān)于白屏問題的描述是這樣的:
"WKWebView 自詡擁有更快的加載速度,更低的內(nèi)存占用,但實際上 WKWebView 是一個多進程組件,
Network Loading 以及 UI Rendering 在其它進程中執(zhí)行。初次適配 WKWebView 的時候,我們也驚
訝于打開 WKWebView 后,App 進程內(nèi)存消耗反而大幅下降,但是仔細觀察會發(fā)現(xiàn),Other Process
的內(nèi)存占用會增加。在一些用 webGL 渲染的復(fù)雜頁面,使用 WKWebView 總體的內(nèi)存占用(App Pr
ocess Memory + Other Process Memory)不見得比 UIWebView 少很多。
在 UIWebView 上當(dāng)內(nèi)存占用太大的時候,App Process 會 crash;而在 WKWebView 上當(dāng)總體的內(nèi)
存占用比較大的時候,WebContent Process 會 crash,從而出現(xiàn)白屏現(xiàn)象。"
總之,就是因為某種原因,Web Content Process奔潰了,從而出現(xiàn)白屏現(xiàn)象。
因為我的項目里,暫時沒有遇到這個問題。所以大家可以先看一下騰訊的解決方案。
此處等待驗證: 需要注意的一點是,我在之前的測試中,發(fā)現(xiàn)貌似上邊提到的重定向失敗也會進入這個方法。但是
處理a標(biāo)簽和_blank
需要通過navigationAction.targetFrame判斷目標(biāo)frame是不是主frame,如果不是主frame,那么就說明是新開一個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ù)自己的需要進行定制。注意alertView點擊之后需要調(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請求的證書驗證
WKWebView中提供了didReceiveAuthenticationChallenge:方法來判斷。我們可以彈個alert讓用戶選擇是否信任,也可以默認(rèn)直接設(shè)置信任。 以下的處理方式朋友分享的一個,源頭可能來自《wkwebview下的https請求》:
/**
https 請求會進這個方法,在里面進行https證書校驗、白名單域名判斷等操作
*/
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
/*
NSURLSessionAuthChallengeUseCredential = 0, 使用證書
NSURLSessionAuthChallengePerformDefaultHandling = 1, 忽略證書(默認(rèn)的處理方式)
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 忽略書證, 并取消這次請求
NSURLSessionAuthChallengeRejectProtectionSpace = 3, 拒絕當(dāng)前這一次, 下一次再詢問
*/
NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
// 判斷服務(wù)器返回的證書類型, 是否是服務(wù)器信任
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef secTrustRef = challenge.protectionSpace.serverTrust;
if (secTrustRef != NULL) {// 信任是否為空
SecTrustResultType result;
OSErr er = SecTrustEvaluate(secTrustRef, &result);
if (er != noErr) {// 是否有錯誤信息
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace,nil);
return;
}else{// 沒有錯誤信息
if (result == kSecTrustResultRecoverableTrustFailure) {// 證書不受信任
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ù)器無法驗證";
NSString *message = [NSString stringWithFormat:@" 是否通過來自%@標(biāo)識為 %@證書為%@的驗證. \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{// 證書受信任
NSURLCredential* credential = [NSURLCredential credentialForTrust:secTrustRef];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
return;
}
}
}else{//信任不為空
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
}else{//非服務(wù)器信任
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
關(guān)于在線播放視頻
需要注意mediaTypesRequiringUserActionForPlayback這個屬性設(shè)置哪些媒體資源需要用戶手動操作一下才能播放,也就是否自動播放。WKAudiovisualMediaTypeNone代表視頻和音頻資料都自動播放。
WKWebViewConfiguration * webConfiguration = [[WKWebViewConfiguration alloc]init];
WKUserContentController *contentController = [[WKUserContentController alloc] init];
// 是否允許HTML5頁面在線播放視頻,否則使用native播放器
webConfiguration.allowsInlineMediaPlayback = YES;
// 是指不需要用戶操作,進入webView頁面視頻自動播放
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這個屬性是設(shè)置了用戶拷貝網(wǎng)頁內(nèi)容的時候的粒度。粒度可能很不好理解。我們直接找個新聞網(wǎng)頁看下設(shè)置之后的效果。當(dāng)設(shè)置為WKSelectionGranularityCharacter, 在iOS9上復(fù)制文本沒有定位光標(biāo)。
具體可以看下:


屏幕旋轉(zhuǎn)
// 屏幕旋轉(zhuǎn)
wkWebView.translatesAutoresizingMaskIntoConstraints = NO;
wkWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
NSURLProtocol
WKWebView中使用NSURLProtocol需要使用私有API,而且用了之后有兩個問題。網(wǎng)上的一些方案可以過審,但是考慮到我們項目并非必要這個需求和使用之后的不確定性以及工作量。最終放棄NSURLProtocol。不過先期也了解了一下,提供大家?guī)灼诲e的文章參考:
參考
強烈建議你把下邊的參考文章也快速看下,作為拓展和補充:
- 《1. WKWebView 的使用》
- 《ObjC WKWebView精講》
- 《WKWebView從入門到趟坑》
- 《WKWebView》
- 《iOS11.3 WKWebView清除cookie所踩的坑!》
- 《WKWebView 的使用和踩過的坑》
- 《WKWebViewTips》
交流

希望能和大家交流技術(shù)
Blog:http://www.lilongcnc.cc