Objective-C的WKWebView學習筆記

WKWebView - Web視圖

WKWebView 是蘋果在iOS 8中引入的新組件,用于顯示交互式web內容的對象冲杀,支持更多的HTML5的特性效床,其官方宣稱高達60fps的滾動刷新率以及內置手勢,并將UIWebViewDelegate與UIWebView拆分成了14類與3個協(xié)議权谁,擁有Safari相同的JavaScript引擎剩檀,可以更少的占用內存。

使用WKWebView類將web內容嵌入到應用程序中時,需要創(chuàng)建一個WKWebView對象,并將它設置為視圖担租,然后向其發(fā)送加載web內容的請求菠镇。使用initWithFrame:configuration: 函數創(chuàng)建一個新的WKWebView對象,使用loadHTMLString:baseURL: 函數加載本地HTML文件,或者使用loadRequest:函數加載web內容嵌莉。使用loading屬性來確定web視圖是否正在加載沿癞,并且stopLoading方法來停止加載具温。將delegate屬性設置為一個符合WKUIDelegate協(xié)議的對象,以跟蹤web內容的加載。如果要讓用戶在網頁歷史中向前和向后移動失仁,可以使用goBack和forward方法作為按鈕的操作拂封。當用戶無法向某個方向移動時,使用canGoBack和canGoForward屬性禁用按鈕刚梭。默認情況下走趋,web視圖會自動將出現在web內容中的電話號碼轉換為電話鏈接。當點擊電話鏈接時授滓,手機應用程序啟動并撥打電話號碼诚啃。可以使用dataDetectorTypes設置此默認行為。還可以使用setMagnification:centeredAtPoint: 函數以編程方式設置web內容第一次在web視圖中顯示時的規(guī)模。此后乡翅,用戶可以使用手勢改變比例波势。

WKWebView常用屬性
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;

屬性描述 :只讀屬性概疆,用于獲取包含Web視圖相關配置詳細信息的對象棋嘲。此屬性返回的是配置對象的副本荣回,所以對該對象所做的更改不會影響Web視圖的配置著蛙。如果沒有使用 initWithFrame:configuration: 方法創(chuàng)建Web視圖,則此屬性包含一個默認配置對象诫隅。

@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;

屬性描述對Web視圖進行導航跟蹤风题、身份驗證等操作的代理對象绕辖,指定的對象必須符合 WKNavigationDelegate協(xié)議昵骤。

@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;

屬性描述Web視圖與IOS用戶界面交互代理對象蹦玫,如點擊Web視圖顯示一個IOS的Alert警告彈窗需要此代理福贞。

@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;

屬性描述 :只讀屬性向瓷,Web視圖的前向列表朱躺。

@property (nullable, nonatomic, readonly, copy) NSString *title;

屬性描述 : 只讀屬性鸡典,頁面標題。這個屬性是兼容WKWebView類的鍵值觀察(KVO)的。

@property (nullable, nonatomic, readonly, copy) NSURL *URL;

屬性描述 :只讀屬性货徙,Web視圖當前顯示網頁的URL胸囱。這個屬性是兼容WKWebView類的鍵值觀察(KVO)的谤职。

@property (nonatomic, readonly, getter=isLoading) BOOL loading;

屬性描述 :只讀屬性漩蟆,一個布爾值捺癞,指示當前Web視圖當前是否正在加載內容。如果接收器仍在加載內容彻犁,則設置為true;否則設置為false钓辆。這個屬性是兼容WKWebView類的鍵值觀察(KVO)的烁落。

@property (nonatomic, readonly) double estimatedProgress;

屬性描述 :只讀屬性,預估當前導航已加載的比例果善。這個值的范圍在0.0到1.0之間,這取決于預計接收到的字節(jié)總數亡资,包括主文檔及其所有潛在的子資源。導航加載完成后幸斥,估計進度保持在1.0,直到新的導航開始,這時估計進度被重置為0.0佑钾。這個屬性是兼容WKWebView類的鍵值觀察(KVO)的窖壕。

\color{red}{例如:監(jiān)聽estimatedProgress屬性鍵值觀察的代碼片段:}

//為webView添加觀察者
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];

//觀察者方法,監(jiān)聽webView加載進度,調整進度條百分比
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        //estimatedProgress當前導航的網頁已經加載的估計值(double:0.0~1.0)
        [self.progressView setProgress:self.webView.estimatedProgress animated:YES];
        self.progressView.progress = self.webView.estimatedProgress;
        if (self.webView.estimatedProgress == 1.0) {
            [self.progressView removeFromSuperview];
        }
    }
}
@property (nonatomic, readonly) BOOL hasOnlySecureContent;

屬性描述 : 一個布爾值逐虚,指示是否已通過安全加密的連接加載頁上的所有資源漓穿。這個屬性是兼容WKWebView類的鍵值觀察(KVO)的沿量。

@property (nonatomic, readonly) BOOL canGoBack;

屬性描述 :只讀屬性撤蚊, 一個布爾值钝计,指示“后退”列表中是否有可導航到的“后退”項缺厉。通常用于后退前進行判斷嗜价。

\color{red}{例如:根據canGoBack判斷web視圖是后退到上一級或是關閉web視圖:}

- (void)goBack {
    //如果可以返回
    if([self.webView canGoBack]) {
        //進行返回
        [self.webView goBack];

    }else{
        //彈出當前視圖控制器
        [self.navigationController popViewControllerAnimated:YES];
    }
}
@property (nonatomic, readonly) BOOL canGoForward;

屬性描述 : 只讀屬性督怜,一個布爾值,指示“前進”列表中是否有可導航到的前進項宵蕉。

@property (nonatomic) BOOL allowsBackForwardNavigationGestures;

屬性描述 :只讀屬性敞恋, 一個布爾值衬横,指示水平滑動手勢是否會觸發(fā)前后列表導航。默認值為“NO”。

WKWebView常用函數
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

函數描述此方法是WKWebView指定初始化方法,返回用指定的框架矩形和配置初始化的Web視圖将塑。接受指定初始值設定項的自定義WKWebViewConfiguration對象来吩,Web視圖會復制指定的配置并將復制的WKWebViewConfiguration對象賦值給configuration屬性责鳍,所以初始化Web視圖后更改配置對象對Web視圖沒有影響嘀略。要使用默認配置值創(chuàng)建Web視圖确买,則調用繼承的initWithFrame: 方法缴罗。

參數 :

frame : 新Web視圖的框架矩形。

configuration : 新Web視圖的配置。

返回值 : 初始化的web Web籽腕,如果對象無法初始化,則為nil疗绣。

- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;

函數描述Web視圖導航到請求的URL比伏。

參數 :

request : 指定要顯示的資源的URL請求。

返回值:一個新的導航對象造成,用于跟蹤請求的加載進度。

- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

函數描述 :加載指定HTML字符串的內容并導航到它。使用此方法可以導航到程序中加載或創(chuàng)建的網頁祟昭。例如差购,可以使用此方法加載應用程序中以編程方式生成的HTML內容。

參數 :

string : 用作網頁內容的字符串。

baseURL :用于解析HTML文檔中的相對URL的基礎URL期升。

返回值:一個新的WKWebView導航墅茉。

\color{red}{一個加載本地Html文件的極其簡單的例子:}

首先需要創(chuàng)建一個Html文件熏版,如圖:

截屏2021-02-03下午10.21.57.png
截屏2021-02-03下午10.22.46.png

極其簡單的Html文件:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <meta content="telephone=no,email=no,address=no" name="format-detection">
        <meta name="viewport\" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="apple-mobile-web-app-capable\" content="yes">
        <meta name="wap-font-scale\" content=\"no\">
    </head>
    <body>
        <h1>第一次練習</h1>
    </body>
</html>

顯示WKWebView視圖:

#import "TestWebViewAndJSInteractiveController.h"

@interface TestWebViewAndJSInteractiveController ()

@property (nonatomic, strong) WKWebView *webView;

@end

@implementation TestWebViewAndJSInteractiveController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createUI];
}

- (void)createUI{
    
    //初始化WKWebView
    self.webView = [[WKWebView alloc]init];
    //設置WKWebView背景色
    self.webView.backgroundColor = HEXCOLOR(0xEEF2F3);
    //添加WKWebView
    [self.view addSubview:self.webView];
    [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    //初始化異常
    NSError *error = nil;
    //讀取正在運行的應用程序的捆綁目錄中對應resource名稱的文件胁赢,并使用給定的編碼格式解析為字符串
    NSString *htmlString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"TestInteractive.html" ofType:nil] encoding:NSUTF8StringEncoding error:&error];
    //設置網頁內容
    [self.webView loadHTMLString:htmlString baseURL:nil];
}

@end

看到頁面就是階段性的勝利:

截屏2021-02-03下午10.34.23.png
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

函數描述執(zhí)行JavaScript字符串结洼。

參數 :

javaScriptString : 要執(zhí)行的JavaScript字符串。

completionHandler : 腳本執(zhí)行完成或失敗時要調用的塊禽车。

WKWebViewConfiguration - Web視圖相關配置

WKWebViewConfiguration是WKWebView的配置類搀罢,實例化WKWebViewConfiguration并訪問其中的屬性設置配置信息,在實例化WKWebView時將WKWebViewConfiguration實例作為參數傳入。

\color{red}{例如:使用WKWebViewConfiguration攜帶偏好設置初始化WKWebView視圖的代碼片段:}

//初始化一個WKWebViewConfiguration對象
WKWebViewConfiguration *config = [WKWebViewConfiguration new];
//初始化偏好設置屬性:preferences
config.preferences = [WKPreferences new];
//默認情況下,最小字體大小為0;
config.preferences.minimumFontSize = 0;
//是否支持JavaScript
config.preferences.javaScriptEnabled = YES;
//不通過用戶交互,是否可以打開窗口
config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
// 檢測各種特殊的字符串:比如電話、網站
config.dataDetectorTypes = UIDataDetectorTypeAll;
// 播放視頻
config.allowsInlineMediaPlayback = YES;

// 交互對象設置
config.userContentController = [[WKUserContentController alloc] init];
        
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.view addSubview:webView];
WKWebViewConfiguration常用屬性
@property (nonatomic, strong) WKProcessPool *processPool;

屬性描述流程池,從中獲取視圖的Web內容流程。初始化Web視圖時,將從指定的池中為其創(chuàng)建新的Web內容流程,或者使用該池中的現有流程。

@property (nonatomic, strong) WKPreferences *preferences;

屬性描述加載Web視圖要使用的偏好設置,可以通過將新的WKPreferences對象分配給此屬性來更改偏好設置财异。

@property (nonatomic, strong) WKUserContentController *userContentController;

屬性描述內容交互控制器拷泽,自己注入JS代碼及注冊JS調用原生方法的對象,在delloc時需要移除注入。

@property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore;

屬性描述網站數據儲存對象,這個屬性根據需求選擇使用默認或自己設置,使用這個對象可以設置不存儲任何數據和移除/獲取緩存數據。

@property (nullable, nonatomic, copy) NSString *applicationNameForUserAgent;

屬性描述以字符串告知把告知瀏覽器一些硬件的信息

例如 : webConfig.applicationNameForUserAgent = @"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)";

@property (nonatomic) BOOL allowsAirPlayForMediaPlayback;

屬性描述 :一個布爾值夺姑,指示是否允許AirPlay播放墩邀。默認值是YES。

@property (nonatomic) WKAudiovisualMediaTypes mediaTypesRequiringUserActionForPlayback;

屬性描述確定哪些媒體類型需要用戶手勢才能開始播放盏浙。

  • WKAudiovisualMediaTypes枚舉值如下:
typedef NS_OPTIONS(NSUInteger, WKAudiovisualMediaTypes) {
    //沒有媒體類型需要用戶手勢才能開始播放
    WKAudiovisualMediaTypeNone = 0,
    //包含音頻的媒體類型需要用戶手勢才能開始播放
    WKAudiovisualMediaTypeAudio = 1 << 0,
    //包含視頻的媒體類型需要用戶手勢才能開始播放
    WKAudiovisualMediaTypeVideo = 1 << 1,
    //所有媒體類型都需要用戶手勢才能開始播放
    WKAudiovisualMediaTypeAll = NSUIntegerMax
}
@property (nonatomic) BOOL allowsInlineMediaPlayback;

屬性描述 :一個布爾值,設置HTML5視頻是否允許網頁內聯播放 废膘,設置為'NO'則會使用本地播放器竹海,默認值是'NO'。在iPhone上將視頻元素添加到HTML文檔時丐黄,還必須包含playinline屬性斋配。

@property (nonatomic) WKSelectionGranularity selectionGranularity;

屬性描述用戶可以交互選擇Web視圖中的內容的粒度級別。可能的值在WKSelectionGranularity中描述许起。默認值是WKSelectionGranularityDynamic十偶。

  • WKSelectionGranularity枚舉值如下:
typedef NS_ENUM(NSInteger, WKSelectionGranularity) {
    //根據選擇自動變化的粒度  
    WKSelectionGranularityDynamic,
    //允許用戶將選擇端點放置在任何字符邊界的粒度
    WKSelectionGranularityCharacter,
}
@property (nonatomic) BOOL allowsPictureInPictureMediaPlayback;

屬性描述 :一個布爾值菩鲜,指示HTML5視頻是否可以播放畫中畫园细,此屬性的默認值為 YES。

@property (nonatomic) WKDataDetectorTypes dataDetectorTypes;

屬性描述表示所需數據檢測類型的枚舉值接校,默認值是WKDataDetectorTypeNone猛频。默認情況下,Web視圖會自動將出現在Web內容中的電話號碼蛛勉,鏈接鹿寻,日歷轉換為鏈接,當鏈接被點擊時诽凌,程序就會撥打該號碼或打開日歷或鏈接毡熏,要關閉這個默認的行為,用WKDataDetectorTypes 設置WKDataDetectorTypePhoneNumber | WKDataDetectorTypeLink | WKDataDetectorTypeCalendarEvent侣诵。

@property (nonatomic) BOOL suppressesIncrementalRendering;

屬性描述 :一個布爾值痢法,指示Web視圖是否會抑制內容呈現,直到將其完全加載到內存中在渲染視圖杜顺。默認值是NO财搁。

@property (nonatomic) BOOL ignoresViewportScaleLimits;

屬性描述 : 一個布爾值,指示Web視圖是否應始終允許縮放網頁躬络,而不管作者的意圖如何尖奔。這將覆蓋用戶可伸縮屬性。默認值為NO穷当。

@property (nonatomic) WKUserInterfaceDirectionPolicy userInterfaceDirectionPolicy;

屬性描述用戶界面元素的方向性提茁。在WKUserInterfaceDirectionPolicy中描述了可能的值。默認值是WKUserInterfaceDirectionPolicyContent馁菜。

//WKUserInterfaceDirectionPolicy枚舉值如下:
typedef NS_ENUM(NSInteger, WKUserInterfaceDirectionPolicy) {
    //用戶界面方向性遵循 CSS / HTML / XHTML規(guī)格
    WKUserInterfaceDirectionPolicyContent,
    //用戶界面方向性遵循視圖的userInterfaceLayoutDirection 屬性
    WKUserInterfaceDirectionPolicySystem,
} 

WKPreferences - 加載Web視圖的偏好設置

一個對象茴扁,用于封裝要應用于加載Web視圖要使用的偏好設置,包括最小字體大小火邓、JavaScript行為和處理欺詐網站的行為丹弱。創(chuàng)建此對象,并將其分配給用于創(chuàng)建web視圖的WKWebViewConfiguration對象的首選項屬性铲咨。

WKPreferences常用屬性
@property (nonatomic) CGFloat minimumFontSize;

屬性描述設置最小字體躲胳,默認值為0。

@property (nonatomic) BOOL javaScriptEnabled;

屬性描述 :一個布爾值纤勒,指示是否啟用 JavaScript坯苹,將此屬性設置為NO會禁用網頁加載或執(zhí)行的 JavaScript,此設置不影響用戶腳本摇天。默認值為YES粹湃。

@property (nonatomic) BOOL javaScriptCanOpenWindowsAutomatically;

屬性描述 :一個布爾值恐仑,指示JavaScript是否可以在沒有用戶交互的情況下打開窗口,默認值為NO为鳄。

@property (nonatomic) BOOL javaEnabled;

屬性描述是否啟用java裳仆, 默認為NO。

@property (nonatomic) BOOL plugInsEnabled;

屬性描述是否啟用插件孤钦,默認為NO歧斟。

@property (nonatomic) BOOL tabFocusesLinks API_AVAILABLE(macosx(10.12.3));

屬性描述如果tabFocusesLinks是YES,那么tab鍵將聚焦鏈接和表單控件偏形。Option鍵暫時反轉此首選項静袖。

WKUserScript - 注入網頁的腳本

Web視圖注入網頁的腳本。當想將自定義腳本代碼注入Web視圖的頁面時俊扭,創(chuàng)建一個WKUserScript對象队橙,使用此對象指定要注入的JavaScript代碼,以及指定在什么時機怎樣去注入這些JavaScript代碼的相關參數萨惑。在創(chuàng)建Web視圖之前捐康,將此對象添加到與Web視圖配置對象(WKWebViewConfiguration的userContentController屬性)關聯的WKUserContentController對象。

WKUserScript常用函數
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;

函數描述返回可以添加WKUserContentController中的初始化用戶腳本咒钟。

參數 :

source :腳本源吹由。

injectionTime : 應該注入腳本的時間。

forMainFrameOnly : 腳本是應注入全局窗口(NO)或只限主窗口(yes)

WKUserScript常用屬性
@property (nonatomic, readonly, copy) NSString *source;

屬性描述 : 只讀屬性朱嘴,腳本源代碼倾鲫。

@property (nonatomic, readonly) WKUserScriptInjectionTime injectionTime;

屬性描述 : 只讀屬性欧引,腳本應該被注入網頁中的時間點捷犹。

  • WKUserScriptInjectionTime枚舉值如下:
typedef NS_ENUM(NSInteger, WKUserScriptInjectionTime) {
    // 在文檔元素創(chuàng)建之后,但在加載任何其他內容之前注入腳本尾组。
    WKUserScriptInjectionTimeAtDocumentStart,
   //在文件完成加載后壤追,但在其他子資源完成加載之前注入該腳本磕道。
    WKUserScriptInjectionTimeAtDocumentEnd
} API_AVAILABLE(macosx(10.10), ios(8.0));
@property (nonatomic, readonly, getter=isForMainFrameOnly) BOOL;

屬性描述 : 只讀屬性,一個布爾值行冰,指示腳本是否僅應注入主窗口(YES)或所有窗口(NO)溺蕉。

WKUserContentController - Web視圖與JavaScript代碼的橋梁

一個對象,用于管理JavaScript代碼和Web視圖之間的交互悼做,以及過濾Web視圖中的內容疯特。WKUserContentController對象在應用程序和運行在Web視圖中的JavaScript代碼之間提供了一座橋梁,使用該對象可完成以下任務:

  • 將JavaScript代碼注入到Web視圖運行的網頁中肛走。

  • 注入可以調用應用原生代碼的自定義JavaScript函數漓雅。

  • 指定自定義過濾器用來防止網頁加載受限制的內容。

創(chuàng)建并配置一個WKUserContentController對象,作為整個Web視圖設置的一部分邻吞。在創(chuàng)建Web視圖之前组题,將該對象分配給WKWebViewConfiguration對象的userContentController屬性。

WKUserContentController常用屬性
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;

屬性描述 :只讀屬性抱冷,與此用戶內容控制器關聯的用戶腳本崔列。

WKUserContentController常用函數
- (void)addUserScript:(WKUserScript *)userScript;

函數描述將指定的腳本注入到網頁的內容中

參數 :

userScript : 添加到Web視圖的當前頁面的用戶腳本徘层。

- (void)removeAllUserScripts;

函數描述刪除所有關聯的用戶腳本峻呕。

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

函數描述注冊要被JavaScript調用的方法名稱,添加scriptMessageHandler將為當前網頁內容的內容領域中添加一個函數window.webkit.messagehandlers.<name>.postmessage(<messagebody>)趣效,即在WKContentWorld的pageWorld屬性的內容領域中。

之后在JavaScript中使window.webkit.messageHandlers.name.postMessage()方法來像應用程序發(fā)送消息猪贪,支持OC中字典跷敬,數組,NSNumber等原生數據類型热押,JavaScript代碼中的name要和調用該函數時注冊的相同西傀。

在應用程序代理對象的-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message回調方法中,會獲取到JavaScript傳遞進來的消息桶癣。

參數 :

scriptMessageHandler : 可以處理JavaScript所發(fā)送消息的對象拥褂,該對象必須采用WKScriptMessageHandler協(xié)議。

name :要被JavaScript調用的方法名稱牙寞,此參數在WKUserContentController對象中必須是唯一的饺鹃,并且不得為空字符串。

- (void)removeScriptMessageHandlerForName:(NSString *)name;

函數描述在JavaScript代碼中移除之前使用addScriptMessageHandler:name: 方法注入的JavaScript可發(fā)送消息的函數间雀。此方法從當前網頁內容的內容領域中移除函數悔详,即從WKContentWorld的pageWorld屬性中獲得的內容領域,如果將函數注入在其他內容領域中惹挟,則此方法不會將其刪除茄螃。

參數 :

name : 要在JavaScript代碼中移除的方法名稱,如果移除的方法名稱不存在則此方法不執(zhí)行任何操作连锯。

- (void)addContentRuleList:(WKContentRuleList *)contentRuleList API_AVAILABLE(macosx(10.13), ios(11.0));

函數描述將指定的內容規(guī)則列表添加到WKUserContentController對象归苍,調用此方法將一組內容篩選規(guī)則應用于Web視圖的配置。

參數 :

contentRuleList : 要添加的規(guī)則列表运怖,規(guī)則列表是從WKContentExtensionStore對象創(chuàng)建或檢索的拼弃。

- (void)removeContentRuleList:(WKContentRuleList *)contentRuleList API_AVAILABLE(macosx(10.13), ios(11.0));

函數描述從WKUserContentController對象中刪除指定的規(guī)則列表,此方法僅從WKUserContentController對象中刪除規(guī)則列表驳规,但仍然可以從用于創(chuàng)建規(guī)則列表的WKContentRuleListStore對象訪問規(guī)則列表肴敛。

參數 :

contentRuleList : 要刪除的規(guī)則列表。

- (void)removeAllContentRuleLists API_AVAILABLE(macosx(10.13), ios(11.0));

函數描述從WKUserContentController對象中刪除所有規(guī)則列表,此方法僅從WKUserContentController對象中刪除規(guī)則列表医男,但仍然可以從用于創(chuàng)建規(guī)則列表的WKContentRuleListStore對象訪問規(guī)則列表砸狞。

\color{red}{JS調用IOS注冊的函數:}

這里我們可以進階了,給JS一個機會镀梭,讓它與IOS進行一次友好的溝通刀森。進階的Html文件,如下:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <meta content="telephone=no,email=no,address=no" name="format-detection">
        <meta name="viewport\" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="apple-mobile-web-app-capable\" content="yes">
        <meta name="wap-font-scale\" content=\"no\">
    </head>
    <body>
        <h1>第一次練習</h1>
        <input name="but" type="button" value="調用IOS" style="height:80px; width:240px; font-size:40px;" onclick = "javascript:btn_Click()"/>
        
    </body>
    
    <script type='text/javascript'>
    function btn_Click(){
        alert("即將觸發(fā)事件");
        window.webkit.messageHandlers.jsCallios.postMessage({
            "hello": "你好",
        });
    }
    </script>
</html>

進階的iOS顯示WKWebView的文件报账,添加了不少好東西研底,監(jiān)聽了頁面加載、監(jiān)聽了Html頁面alert彈框展示透罢、監(jiān)聽了JS調用IOS的函數榜晦,如下:

#import "TestWebViewAndJSInteractiveController.h"

@interface TestWebViewAndJSInteractiveController ()<UIScrollViewDelegate,WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>

@property (nonatomic, strong) WKWebView *webView;

@end

@implementation TestWebViewAndJSInteractiveController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createUI];
}

- (void)dealloc{
    //刪除腳本消息處理程序
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"jsCallios"];
}


- (void)createUI{
    //初始化一個WKWebViewConfiguration對象
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
    //初始化偏好設置屬性:preferences
    configuration.preferences = [WKPreferences new];
    //默認情況下,最小字體大小為0;
    configuration.preferences.minimumFontSize = 0;
    //是否支持JavaScript
    configuration.preferences.javaScriptEnabled = YES;
    //不通過用戶交互羽圃,是否可以打開窗口
    configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
    //注冊要被js調用的jsCallios方法
    [configuration.userContentController addScriptMessageHandler:self name:@"jsCallios"];
    //初始化WKWebView
    self.webView = [[WKWebView alloc]initWithFrame:CGRectZero configuration:configuration];
    //設置WKWebView滾動視圖代理
    self.webView.scrollView.delegate = self;
    //設置WKWebView用戶界面委托代理
    self.webView.UIDelegate = self;
    //設置WKWebView導航代理
    self.webView.navigationDelegate = self;
    //設置WKWebView背景色
    self.webView.backgroundColor = HEXCOLOR(0xEEF2F3);
    //添加WKWebView
    [self.view addSubview:self.webView];
    [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    //初始化異常
    NSError *error = nil;
    //讀取正在運行的應用程序的捆綁目錄中對應resource名稱的文件乾胶,并使用給定的編碼格式解析為字符串
    NSString *htmlString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"TestInteractive.html" ofType:nil] encoding:NSUTF8StringEncoding error:&error];
    //設置網頁內容
    [self.webView loadHTMLString:htmlString baseURL:nil];
}

#pragma mark -- WKNavigationDelegate
//監(jiān)聽頁面加載狀態(tài)

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"開始加載web頁面");
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"頁面加載完成");
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    NSLog(@"頁面加載失敗");
}

#pragma mark -- WKUIDelegate
//顯示一個alert彈框(iOS展示Html頁面alert彈框時使用)

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
    
}

#pragma mark - WKScriptMessageHandler
//監(jiān)聽js調用注冊的函數

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    //判斷執(zhí)行的函數名稱
    if ([message.name isEqualToString:@"jsCallios"]) {
        NSDictionary *jsCalliosDic = (NSDictionary *)message.body;
        //獲取消息參數
        if([jsCalliosDic.allKeys containsObject:@"hello"]){
            //顯示提示
            [self.view makeToast:[jsCalliosDic objectForKey:@"hello"]duration:1.5 position:CSToastPositionCenter];
        }
    }
}

@end

這次不但能看到頁面,按鈕還可以調用IOS函數呢:

Jietu20210203-234137.gif

\color{red}{IOS調用JS函數:}

正所謂來而不往非禮也朽寞,既然JS已經調用了IOS函數识窿,IOS怎么能不給JS回應呢,這時候evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler函數就要閃亮登場了脑融。

再次進階的Html文件喻频,增加了顯示IOS傳遞給JS的文本的Div:

<!DOCTYPE html>

<html>
    
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
        <meta content="telephone=no,email=no,address=no" name="format-detection">
        <meta name="viewport\" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="apple-mobile-web-app-capable\" content="yes">
        <meta name="wap-font-scale\" content=\"no\">
    </head>
    
    <body>
        <h1>第一次練習</h1>
        
        <input name="but" type="button" value="調用IOS" style="height:80px; width:240px; font-size:40px;" onclick = "javascript:btn_Click()"/>
        
        <div id = "returnValue" style = "font-size: 40px; border: 1px dotted; height: 200px; margin-top:50px;">
        
    </body>
    
    <script type='text/javascript'>
        
    function btn_Click(){
        alert("即將觸發(fā)事件");
        window.webkit.messageHandlers.jsCallios.postMessage({
            "hello": "你好",
        });
    }
    
    function iosCalljs(message){
        document.getElementById("returnValue").innerHTML = message;
    }
    
    </script>
    
</html>

以及再次進階的IOS文件:

#import "TestWebViewAndJSInteractiveController.h"

@interface TestWebViewAndJSInteractiveController ()<UIScrollViewDelegate,WKUIDelegate,WKNavigationDelegate,WKScriptMessageHandler>

@property (nonatomic, strong) WKWebView *webView;

@end

@implementation TestWebViewAndJSInteractiveController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"IOS與JS交互";
    [self createUI];
}

- (void)dealloc{
    //刪除腳本消息處理程序
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"jsCallios"];
}


- (void)createUI{
    
    ///調用JS函數的按鈕
    UIButton *iosCallJsButton  = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.view addSubview:iosCallJsButton];
    iosCallJsButton.layer.cornerRadius = 22.0;
    iosCallJsButton.layer.masksToBounds = YES;
    iosCallJsButton.titleLabel.font = [UIFont systemFontOfSize:16];
    iosCallJsButton.backgroundColor = [UIColor redColor];
    [iosCallJsButton setTitle:@"調用JS函數" forState:UIControlStateNormal];
    [iosCallJsButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [iosCallJsButton addTarget:self action:@selector(CallJsFunction) forControlEvents:UIControlEventTouchUpInside];
    
    [iosCallJsButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(self.view.mas_bottom).offset(- 10);
        make.left.equalTo(self).offset(10 * 2.0);
        make.right.equalTo(self).offset(-10 * 2.0);
        make.height.mas_equalTo(44);
    }];
    
    //初始化一個WKWebViewConfiguration對象
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
    //初始化偏好設置屬性:preferences
    configuration.preferences = [WKPreferences new];
    //默認情況下,最小字體大小為0;
    configuration.preferences.minimumFontSize = 0;
    //是否支持JavaScript
    configuration.preferences.javaScriptEnabled = YES;
    //不通過用戶交互肘迎,是否可以打開窗口
    configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
    //注冊要被js調用的jsCallios方法
    [configuration.userContentController addScriptMessageHandler:self name:@"jsCallios"];
    //初始化WKWebView
    self.webView = [[WKWebView alloc]initWithFrame:CGRectZero configuration:configuration];
    //設置WKWebView滾動視圖代理
    self.webView.scrollView.delegate = self;
    //設置WKWebView用戶界面委托代理
    self.webView.UIDelegate = self;
    //設置WKWebView導航代理
    self.webView.navigationDelegate = self;
    //設置WKWebView背景色
    self.webView.backgroundColor = HEXCOLOR(0xEEF2F3);
    //添加WKWebView
    [self.view addSubview:self.webView];
    [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(iosCallJsButton.mas_top);
        make.top.left.right.equalTo(self.view);
    }];
    //初始化異常
    NSError *error = nil;
    //讀取正在運行的應用程序的捆綁目錄中對應resource名稱的文件甥温,并使用給定的編碼格式解析為字符串
    NSString *htmlString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"TestInteractive.html" ofType:nil] encoding:NSUTF8StringEncoding error:&error];
    //設置網頁內容
    [self.webView loadHTMLString:htmlString baseURL:nil];
}

///調用JS函數
- (void)CallJsFunction{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.webView evaluateJavaScript:@"iosCalljs('IOS給您拜年了')" completionHandler:^(id response, NSError *error) {
            NSLog(@"%@",error);
        }];
    });
}

#pragma mark -- WKNavigationDelegate
//監(jiān)聽頁面加載狀態(tài)

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"開始加載web頁面");
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{
    NSLog(@"頁面加載完成");
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{
    NSLog(@"頁面加載失敗");
}

#pragma mark -- WKUIDelegate
//顯示一個alert彈框(iOS展示Html頁面alert彈框時使用)

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
    
}

#pragma mark - WKScriptMessageHandler
//監(jiān)聽js調用注冊的函數

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    //判斷執(zhí)行的函數名稱
    if ([message.name isEqualToString:@"jsCallios"]) {
        NSDictionary *jsCalliosDic = (NSDictionary *)message.body;
        //獲取消息參數
        if([jsCalliosDic.allKeys containsObject:@"hello"]){
            //顯示提示
            [self.view makeToast:[jsCalliosDic objectForKey:@"hello"]duration:1.5 position:CSToastPositionCenter];
        }
    }
}

@end

效果如圖 :

Jietu20210205-003635.gif

UIProgressView - 進度條視圖

描述任務進度隨時間變化的視圖,UIProgressView類提供了一些屬性膜宋,用于管理進度條的樣式窿侈,以及獲取和設置與任務進度相關的值。

UIProgressView初始化函數
- (instancetype)initWithProgressViewStyle:(UIProgressViewStyle)style;

函數描述 : 初始化進度條秋茫,并設置進度條樣式史简。

  • UIProgressViewStyle枚舉值如下:
typedef NS_ENUM(NSInteger, UIProgressViewStyle) {
    UIProgressViewStyleDefault,     // 正常的進度條
    UIProgressViewStyleBar __TVOS_PROHIBITED,     // 用于工具欄
};
UIProgressView常用屬性
@property(nonatomic) float progress;

屬性描述進度條顯示的當前進度。當前進度由0.0到1.0(包括1.0)之間的浮點值表示肛著,其中1.0表示任務的完成圆兵。默認值是0.0,小于0.0和大于1.0的值都會受到這個區(qū)間限制枢贿。

@property(nonatomic, strong, nullable) UIColor* progressTintColor

屬性描述設置進度條顏色殉农。

@property(nonatomic, strong, nullable) UIColor* trackTintColor

屬性描述設置進度條未加載位置顏色

@property(nonatomic, strong, nullable) UIImage* progressImage

屬性描述設置進度條圖片局荚。

@property(nonatomic, strong, nullable) UIImage* trackImage

屬性描述設置進度條未加載位置圖片

WKNavigationDelegate - Web視圖導航代理

WKNavigationDelegate 協(xié)議方法可以幫助你實現在Web視圖接受超凳,加載和完成導航請求的過程中觸發(fā)的自定義行為愈污。當用戶嘗試導航Web內容時,Web視圖與其導航代理協(xié)調以管理任何轉換轮傍。例如暂雹,可以使用這些方法來限制從內容中的特定鏈接進行導航。還可以使用它們來跟蹤請求的進度创夜,并對錯誤和身份驗證挑戰(zhàn)做出響應杭跪。

WKNavigationDelegate常用函數
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

函數描述在發(fā)送請求之前,決定是否允許或取消導航驰吓。如果不實現此方法涧尿,web視圖將加載請求,或者(如果合適的話)將請求轉發(fā)給另一個應用程序檬贰。

參數 :

webView : 調用委托方法的web視圖姑廉。

navigationAction : 關于觸發(fā)導航請求的操作的描述信息。

decisionHandler : 要調用的決策處理程序偎蘸,以允許或取消導航庄蹋。參數是枚舉類型WKNavigationActionPolicy的常量之一。

  • WKNavigationActionPolicy枚舉值如下:
typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
    WKNavigationActionPolicyCancel, //取消導航
    WKNavigationActionPolicyAllow, //允許導航繼續(xù)
} API_AVAILABLE(macosx(10.10), ios(8.0));
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

函數描述頁面開始加載web內容時調用

參數 :

webView : 調用委托方法的web視圖迷雪。

navigation : 開始加載頁的導航對象。

- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;

函數描述當web內容開始返回時調用虫蝶。

參數 :

webView : 調用委托方法的web視圖章咧。

navigation : 開始加載頁的導航對象。

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;

函數描述頁面加載完成之后調用能真。

參數 :

webView : 調用委托方法的web視圖赁严。

navigation : 完成的導航對象。

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;

函數描述頁面加載失敗時調用 (web視圖加載內容時發(fā)生錯誤)粉铐。

參數 :

webView : 調用委托方法的web視圖疼约。

navigation : 開始加載頁的導航對象。

error : 發(fā)生的錯誤蝙泼。

- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;

函數描述web視圖導航過程中發(fā)生錯誤時調用程剥。

參數 :

webView : 調用委托方法的web視圖。

navigation : 開始加載頁的導航對象汤踏。

error : 發(fā)生的錯誤织鲸。

- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

函數描述當web視圖收到服務器重定向時調用

參數 :

webView : 調用委托方法的web視圖溪胶。

navigation : 接收到服務器重定向的導航對象搂擦。

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

函數描述在收到響應之后決定是否跳轉

參數 :

webView :調用委托方法的web視圖哗脖。

navigationResponse : 有關導航響應的描述性信息瀑踢。

decisionHandler :當應用程序決定是否允許或取消導航時要調用的塊扳还。塊采用單個參數,該參數必須是枚舉類型WKNavigationResponsePolicy的常量之一橱夭。

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));

函數描述當Web視圖的Web內容進程終止時調用氨距。

參數 :

webView : 調用委托方法的web視圖。

- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

函數描述在web視圖需要響應身份驗證時調用徘钥。如果不實現此方法衔蹲,web視圖將使用nsurlsessionauthchallenge erejectprotectionspace配置來響應身份驗證。

參數 :

webView : 接受身份驗證的web視圖呈础。

challenge : 身份驗證舆驶。

completionHandler : 必須調用完成處理程序才能響應該挑戰(zhàn)。配置參數是枚舉類型的常量之一 NSURLSessionAuthChallengeDisposition

  • NSURLSessionAuthChallengeDisposition枚舉值如下:
typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) {
    /*使用指定的憑據而钞,它可以是nil*/
    NSURLSessionAuthChallengeUseCredential = 0,
    /*對驗證要求的默認處理-就像未實現此委托一樣沙廉;忽略憑據參數 */                                       
    NSURLSessionAuthChallengePerformDefaultHandling = 1,    
    /*整個請求將被取消;憑證參數將被忽略 */                          
    NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,
   /* 此驗證要求被拒絕臼节,應嘗試下一個身份驗證保護空間撬陵;忽略憑據參數 */                       
    NSURLSessionAuthChallengeRejectProtectionSpace = 3,                               
} NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);

\color{red}{例如 :當使用 Https 協(xié)議加載web內容時,使用的證書不合法或者證書過期時:}

- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        if ([challenge previousFailureCount] == 0) {
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
        } else {
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
        }
    } else {
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

WKScriptMessageHandler - 處理JavaScript消息

符合WKScriptMessageHandler協(xié)議的類提供了一種方法网缝,用于從網頁中運行的JavaScript中接收消息巨税。當應用程序需要在web視圖中響應JavaScript消息時,需要采用WKScriptMessageHandler協(xié)議粉臊。當JavaScript代碼發(fā)送專門針對消息處理程序的消息時草添,WebKit會調用處理程序的userContentController:didReceiveScriptMessage:方法,使用該方法來實現響應處理扼仲。

WKScriptMessageHandler常用函數
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

函數描述當從網頁接收到腳本消息時調用远寸。

參數 :

userContentController : 調用委托方法的WKUserContentController

message : 收到的腳本消息

WKUIDelegate - 代表網頁呈現本機用戶界面

WKUIDelegate 類是網頁視圖的用戶界面委托協(xié)議,提供了代表網頁呈現本機用戶界面元素的方法屠凶。Web視圖用戶界面代理實現該協(xié)議以控制新窗口的打開驰后,增強用戶單擊元素時顯示的默認菜單項的行為,并執(zhí)行其他與用戶界面相關的任務矗愧。這些方法可以作為處理JavaScript或其他插件內容的結果來調用灶芝。默認的Web視圖實現假設每個Web視圖有一個窗口,因此非傳統(tǒng)的用戶界面可能會實現用戶界面代理贱枣。

WKUIDelegate常用函數
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;

函數描述顯示JavaScript警告面板监署。為了用戶的安全,你的應用程序應該注意一個特定的網站控制這個面板的內容纽哥。識別控制網站的簡單forumla是frame.request.URL.host钠乏。面板應該只有一個OK按鈕。如果不實現此方法春塌,web視圖的行為將與用戶選擇OK按鈕一樣晓避。

參數 :

webView : 調用委托方法的web視圖簇捍。

message : 要顯示的消息。

frame : 有關JavaScript發(fā)起此調用的窗口的信息俏拱。

completionHandler : 在警報面板被取消后調用的完成處理程序暑塑。

\color{red}{例如:顯示一個 JavaScript 警告彈窗:}

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
    
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;

函數描述顯示一個 JavaScript 確認面板。為了用戶安全锅必,您的應用程序應該注意這樣一個事實事格,即一個特定的網站控制了這個面板中的內容。識別控制網站的簡單forumla是frame.request.URL.host搞隐。面板應該有兩個按鈕驹愚,比如OK和Cancel。如果不實現此方法劣纲,web視圖的行為將與用戶選擇Cancel按鈕一樣逢捺。

參數 :

webView : 調用委托方法的web視圖。

message : 要顯示的消息癞季。

frame : 有關JavaScript發(fā)起此調用的窗口的信息辛燥。

completionHandler : 在確認面板被取消后調用的完成處理程序歪脏。如果用戶選擇OK,則傳遞YES;如果用戶選擇Cancel碍岔,則傳遞NO竿音。

\color{red}{例如:顯示一個 JavaScript 確認面板:}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;

函數描述顯示一個 JavaScript 文本輸入面板拒迅。為了用戶安全套蒂,您的應用程序應該注意這樣一個事實与境,即一個特定的網站控制了這個面板中的內容。識別控制網站的簡單forumla是frame.request.URL.host郊楣。面板應該有兩個按鈕,比如OK和Cancel瓤荔,以及一個輸入文本的字段净蚤。如果不實現此方法,web視圖的行為將與用戶選擇Cancel按鈕一樣输硝。

參數 :

webView : 調用委托方法的web視圖今瀑。

prompt : 顯示提示符。

defaultText:要在文本輸入字段中顯示的初始文本点把。

frame : 有關JavaScript發(fā)起此調用的窗口的信息橘荠。

completionHandler :完成處理程序調用后的文本,輸入面板已被取消郎逃。如果用戶選擇OK哥童,則傳遞輸入的文本,否則為nil褒翰。

\color{red}{例如:顯示一個 JavaScript 文本輸入面板:}

- (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:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

綜合使用示例代碼:

//
//  ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

//
//  ViewController.m

#import "ViewController.h"
#import "Masonry.h"
#import "BaseWebViewControlle.h"

//十六進制顏色宏定義
#define HEXCOLOR(c)                                  \
[UIColor colorWithRed:((c >> 16) & 0xFF) / 255.0 \
green:((c >> 8) & 0xFF) / 255.0  \
blue:(c & 0xFF) / 255.0         \
alpha:1.0]

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    self.navigationItem.title = @"百度一下,你就知道";
    [self setButton];
}

- (void)setButton{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setTitle:@"百度一下" forState:UIControlStateNormal];
    button.titleLabel.font = [UIFont systemFontOfSize:15];
    [button addTarget:self action:@selector(goBaiDu) forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:button];
    [button mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(75, 35));
        make.center.equalTo(self.view);
    }];
    
    ///按鈕漸變色
    //CAGradientLayer繼承CALayer贮懈,可以設置漸變圖層
    CAGradientLayer *grandientLayer = [[CAGradientLayer alloc] init];
    grandientLayer.frame = CGRectMake(0, 0, 75.0, 35.0);
    [button.layer addSublayer:grandientLayer];
    [button.layer insertSublayer:grandientLayer atIndex:0];
    //設置漸變的方向 左上(0,0)  右下(1,1)
    grandientLayer.startPoint = CGPointZero;
    grandientLayer.endPoint = CGPointMake(1.0, 0.0);
    //colors漸變的顏色數組 這個數組中只設置一個顏色是不顯示的
    grandientLayer.colors = @[(id)HEXCOLOR(0x5C9CDC).CGColor, (id)HEXCOLOR(0x657CDA).CGColor];
    grandientLayer.type = kCAGradientLayerAxial;
    
}

- (void)goBaiDu{
    BaseWebViewControlle *controller = [[BaseWebViewControlle alloc]init];
    controller.url = @"https://www.baidu.com";
    [self.navigationController pushViewController:controller animated:YES];
}

@end

//
//  BaseWebViewControlle.h

#import <UIKit/UIKit.h>

@interface BaseWebViewControlle : UIViewController

@property (nonatomic, copy) NSString *url;

@end

//
//  BaseWebViewController.m


#import "BaseWebViewControlle.h"
#import <WebKit/WebKit.h>
#import "BaseWebViewTitleView.h"
#import "Masonry.h"

@interface BaseWebViewControlle ()<WKNavigationDelegate,WKScriptMessageHandler,WKUIDelegate >


@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) UIProgressView *progressView;
//NSRegularExpression正則表達式專用類
@property (nonatomic,strong) NSRegularExpression *goodsExpression;
@property (nonatomic,strong) NSRegularExpression *loginExpression;
@property (nonatomic,strong) NSRegularExpression *cartExpression;
@end

@implementation BaseWebViewControlle

///該方法只會在控制器加載完view時被調用,viewDidLoad通常不會被第二次調用除非這個view因為某些原因沒有及時加載出來
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    [self setUpCommonHeader];
    
    self.view.backgroundColor = [UIColor whiteColor];
    //創(chuàng)建webview配置對象
    WKWebViewConfiguration *webConfig = [[WKWebViewConfiguration alloc] init];
    // 設置偏好設置
    webConfig.preferences = [[WKPreferences alloc] init];
    // 設置最小字體匀泊,默認值為0
    webConfig.preferences.minimumFontSize = 10;
    // 是否啟用 javaScript,默認值為YES
    webConfig.preferences.javaScriptEnabled = YES;
    // 在iOS上默認為NO朵你,表示不能自動通過窗口打開
    webConfig.preferences.javaScriptCanOpenWindowsAutomatically = NO;
    // web內容處理池
    webConfig.processPool = [[WKProcessPool alloc] init];
    // 將所有cookie以document.cookie = 'key=value';形式進行拼接
    
    //然而這里的單引號一定要注意是英文的
    //格式  @"document.cookie = 'key1=value1';document.cookie = 'key2=value2'";
    NSString *cookie = [self getCookie];
    
    //注入js修改返回按鈕的點擊事件
    NSString *scriptStr =  [NSString stringWithFormat:@"function backHomeClick_test(){window.webkit.messageHandlers.backHomeClick_test.postMessage(null);}(function(){document.getElementsByClassName('sb-back')[0].href = 'javascript:window.backHomeClick_test()';}());"];
    //WKUserScript 對象表示可以注入到網頁中的腳本
    WKUserScript *userScript = [[WKUserScript alloc] initWithSource:scriptStr injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    
    //內容交互控制器各聘,自己注入JS代碼及JS調用原生方法注冊
    WKUserContentController* userContentController = WKUserContentController.new;
    //添加腳本消息處理程序
    [userContentController addScriptMessageHandler:self name:@"backHomeClick_test"];
    //加cookie給h5識別,表明在iOS端打開該地址
    WKUserScript * cookieScript = [[WKUserScript alloc]
                                   initWithSource: cookie
                                   injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    //添加用戶腳本
    [userContentController addUserScript:cookieScript];
    //添加用戶腳本
    [userContentController addUserScript:userScript];
    
    webConfig.userContentController = userContentController;
    
    //初始化webView
    self.webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) configuration:webConfig];
    //添加webView
    [self.view addSubview:self.webView];
    //設置背景顏色
    self.webView.backgroundColor = [UIColor whiteColor];
    //設置代理
    self.webView.navigationDelegate = self;
    //設置代理
    self.webView.UIDelegate = self;
    //為webView添加觀察者
    [self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
    //添加腳本消息處理程序
    [[_webView configuration].userContentController addScriptMessageHandler:self name:@"historyGo"];
    //獲取狀態(tài)欄的Frame
    CGRect statusRect = [[UIApplication sharedApplication] statusBarFrame];
    //獲取導航欄的Frame
    CGRect navigationRect = self.navigationController.navigationBar.frame;
    //設置內邊距
    [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view).with.insets(UIEdgeInsetsMake(statusRect.size.height + navigationRect.size.height ,0,0,0));
    }];
    //UIProgressView用戶界面進度視圖
    self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
    //進度條顏色
    self.progressView.progressTintColor = [UIColor purpleColor];
    //添加進度視圖
    [self.webView addSubview:self.progressView];
    //設置約束
    [self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.right.equalTo(self.webView);
        make.height.mas_equalTo(2.0);
    }];
    //加載請求
    [self loadRequestWithUrlString:self.url];
    
}

///該方法會在view要被顯示出來之前被調用抡医。這總是會發(fā)生在ViewDidload被調用之后并且每次view顯示之前都會調用該方法躲因。
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:NO animated:YES];
}

///視圖被銷毀,此處需要對你在init和viewDidLoad中創(chuàng)建的對象進行釋放
- (void)dealloc {
    //移除觀察者
    [self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
}

- (void)setUpCommonHeader {
    if (self.navigationController.viewControllers.count > 1 ||
        self.presentingViewController) {
        UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [backButton addTarget:self action:@selector(backToPreviousViewController) forControlEvents:UIControlEventTouchUpInside];
        backButton.frame = CGRectMake(0, 0, 25.0, 25.0);
        backButton.imageEdgeInsets = UIEdgeInsetsMake(0, -12, 0, 0);
        [backButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateNormal];
        [backButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateHighlighted];
        UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
        self.navigationItem.leftBarButtonItem = backBarButtonItem;
    }
}

///返回上一個視圖控制器
- (void)backToPreviousViewController {
    [self goBack];
}

- (void)goBack {
    //如果可以返回
    if([self.webView canGoBack]) {
        //進行返回
        [self.webView goBack];

    }else{
        [self.navigationController popViewControllerAnimated:YES];
    }
}

///加載請求
- (void)loadRequestWithUrlString:(NSString *)urlString {
    // 在此處獲取返回的cookie
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    //NSHTTPCookieStorage,提供了管理所有NSHTTPCookie對象的接口,NSHTTPCookieStorage類采用單例的設計模式
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    
    for (NSHTTPCookie *cookie in [cookieJar cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    // cookie重復忌傻,先放到字典進行去重大脉,再進行拼接
    for (NSString *key in cookieDic) {
        NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
        [cookieValue appendString:appendString];
    }
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    [request addValue:cookieValue forHTTPHeaderField:@"Cookie"];
    //加載請求
    [self.webView loadRequest:request];
}

- (NSString*) getCookie {
    // 在此處獲取返回的cookie
    NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
    NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    
    for (NSHTTPCookie *cookie in [cookieJar cookies]) {
        [cookieDic setObject:cookie.value forKey:cookie.name];
    }
    // cookie重復,先放到字典進行去重芯勘,再進行拼接
    for (NSString *key in cookieDic) {
        NSString *appendString = [NSString stringWithFormat:@"document.cookie = '%@=%@';", key, [cookieDic valueForKey:key]];
        [cookieValue appendString:appendString];
    }
    return cookieValue;
}

- (BOOL)canHandleUrl:(NSString *)url {
    if([self.loginExpression matchesInString:url options:0 range:NSMakeRange(0, url.length)].count > 0){
        return YES;
    } else if([self.goodsExpression matchesInString:url options:0 range:NSMakeRange(0, url.length)].count > 0){
        return YES;
    } else if([self.cartExpression matchesInString:url options:1 range:NSMakeRange(0, url.length)].count > 0) {
        
        return YES;
    }
    return NO;
}

///刪除空格和換行符
- (NSString *)removeSpaceAndNewline:(NSString *)str {
    
    NSString *temp = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    temp = [temp stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    temp = [temp stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    return temp;
}

/// 觀察者方法箱靴,監(jiān)聽webView加載進度,調整進度條百分比
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        //estimatedProgress當前導航的網頁已經加載的估計值(double:0.0~1.0)
        [self.progressView setProgress:self.webView.estimatedProgress animated:YES];
        self.progressView.progress = self.webView.estimatedProgress;
        if (self.webView.estimatedProgress == 1.0) {
            [self.progressView removeFromSuperview];
        }
    }
}

#pragma make -- WKNavigationDelegate

/// 在發(fā)送請求之前荷愕,決定是否允許或取消導航
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    
    NSURL *URL = navigationAction.request.URL;
    
    NSString *scheme = [URL scheme];
    
    if ([self canHandleUrl:navigationAction.request.URL.absoluteString]) {
        
        decisionHandler(WKNavigationActionPolicyCancel);
        
    } else if ([scheme isEqualToString:@"tel"]) {
        
        NSString *resourceSpecifier = [URL resourceSpecifier];
        NSString *callPhone = [NSString stringWithFormat:@"telprompt:%@", resourceSpecifier];
        /// 防止iOS 10及其之后衡怀,撥打電話系統(tǒng)彈出框延遲出現
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:callPhone] options:@{}
           completionHandler:^(BOOL success) {
               NSLog(@"Open %@: %d",scheme,success);
           }];
        decisionHandler(WKNavigationActionPolicyAllow);
        
    } else {
        
        //如果是跳轉一個新頁面
        if (navigationAction.targetFrame == nil) {
            [webView loadRequest:navigationAction.request];
        }
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

/// 頁面加載完成之后調用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    
    NSString *tempString = [NSString stringWithFormat:@"document.getElementsByClassName('header-middle')[0].innerHTML"];
    [webView evaluateJavaScript:tempString completionHandler:^(id Result, NSError * _Nullable error) {
        if (!error) {
            NSString *title = [self removeSpaceAndNewline:Result];
            NSError *error = nil;
            //  判斷字符串是否包含html標簽,包含則設置標題為webView.title
            NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"^<(\\s+|\\S+)>$" options:NSRegularExpressionCaseInsensitive error:&error];
            NSArray *result = [regularExpression matchesInString:title options:NSMatchingReportProgress range:NSMakeRange(0, title.length)];
            if (result.count) {
                [self setTitle:webView.title];
            } else {
                [self setTitle:title];
            }
            
        } else {
            [self setTitle:webView.title];
        }
    }];
    
}

/// 頁面開始加載web內容時調用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
    NSString *url = webView.URL.absoluteString;
    NSLog(@"%@",url);
}

/// 接收到服務器重定向之后調用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
    NSLog(@"%@",webView.URL);
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:webView.URL];
    [self.webView loadRequest:request];
}

/// 在收到響應之后決定是否跳轉
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    
    NSLog(@"%@",navigationResponse.response.URL.absoluteString);
    //允許跳轉
    decisionHandler(WKNavigationResponsePolicyAllow);
    //不允許跳轉
    //decisionHandler(WKNavigationResponsePolicyCancel);
}

///頁面加載失敗時調用 (web視圖加載內容時發(fā)生錯誤)
-(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error{
    //重定向要忽略一個-999的錯誤安疗,代理檢測到這個錯誤可能先執(zhí)行該方法再去重定向
    if(error.code == -999){
        return;
    }
    NSLog(@"加載錯誤時候才調用抛杨,錯誤原因=%@",error);
    
}

/// web視圖導航過程中發(fā)生錯誤時調用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
    NSLog(@"%@", error);
}

#pragma make -- WKScriptMessageHandler

///當從網頁接收到腳本消息時調用
//OC在JS調用方法做的處理
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    if ([message.name isEqualToString:@"backHomeClick_test"]) {
        [self backToPreviousViewController];
    }
}

#pragma make -- WKUIDelegate

///顯示一個 JavaScript 警告彈窗
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
    
}

///顯示一個 JavaScript 確認面板
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

///顯示一個 JavaScript 文本輸入面板
- (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:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

@end

運行:


Untitled.gif

關于加載WKWebView白屏問題的記錄

處理辦法,轉載自iOS__峰的博客:

自ios8推出wkwebview以來荐类,極大改善了網頁加載速度及內存泄漏問題怖现,逐漸全面取代笨重的UIWebview。盡管高性能玉罐、高刷新的WKWebview在混合開發(fā)中大放異彩表現優(yōu)異屈嗤,但加載網頁過程中出現異常白屏的現象卻仍然屢見不鮮,且現有的api協(xié)議處理捕捉不到這種異常case吊输,造成用戶無用等待體驗很差饶号。

針對業(yè)務場景需求,實現加載白屏檢測季蚂∶4考慮采用字節(jié)跳動團隊提出的webview優(yōu)化技術方案。在合適的加載時機對當前webview可視區(qū)域截圖扭屁,并對此快照進行像素點遍歷算谈,如果非白屏顏色的像素點超過一定的閾值,認定其為非白屏料滥,反之重新加載請求然眼。

IOS官方提供了簡易的獲取webview快照接口,通過異步回調拿到當前可視區(qū)域的屏幕截圖幔欧。如下:

- (void)takeSnapshotWithConfiguration:(nullable WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage * _Nullable snapshotImage, NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0));

函數描述獲取WKWebView可見視口的快照罪治。如果WKSnapshotConfiguration為nil丽声,則該方法將快照WKWebView并創(chuàng)建一個圖像,該圖像是WKWebView邊界的寬度觉义,并縮放到設備規(guī)模雁社。completionHandler被用來傳遞視口內容的圖像或錯誤。

參數 :

snapshotConfiguration : 指定如何配置快照的對象

completionHandler : 快照就緒時要調用的塊晒骇。

其中snapshotConfiguration 參數可用于配置快照大小范圍霉撵,默認截取當前客戶端整個屏幕區(qū)域。由于可能出現導航欄成功加載而內容頁卻空白的特殊情況洪囤,導致非白屏像素點數增加對最終判定結果造成影響徒坡,考慮將其剔除。如下:

- (void)judgeLoadingStatus:(WKWebView *)webview {
    if (@available(iOS 11.0, *)) {
        if (webView && [webView isKindOfClass:[WKWebView class]]) {
 
            CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //狀態(tài)欄高度
            CGFloat navigationHeight =  webView.viewController.navigationController.navigationBar.frame.size.height; //導航欄高度
            WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
            shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, _webView.bounds.size.width, (_webView.bounds.size.height - navigationHeight - statusBarHeight)); //僅截圖檢測導航欄以下部分內容
            [_webView takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                //todo
            }];
        }
    }
}

縮放快照,為了提升檢測性能瘤缩,考慮將快照縮放至1/5喇完,減少像素點總數,從而加快遍歷速度剥啤。如下 :

- (UIImage *)scaleImage: (UIImage *)image {
    CGFloat scale = 0.2;
    CGSize newsize;
    newsize.width = floor(image.size.width * scale);
    newsize.height = floor(image.size.height * scale);
    if (@available(iOS 10.0, *)) {
        UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
          return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                        [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                 }];
    }else{
        return image;
    }
}  

縮小前后性能對比(實驗環(huán)境:iPhone11同一頁面下):

縮放前白屏檢測

image
image

耗時20ms锦溪。

縮放后白屏檢測

image
image

耗時13ms。

注意這里有個小坑府怯。由于縮略圖的尺寸在原圖寬高縮放系數后可能不是整數刻诊,在布置畫布重繪時默認向上取整,這就造成畫布比實際縮略圖大(混蛋啊 摔N)则涯。在遍歷縮略圖像素時,會將圖外畫布上的像素納入考慮范圍冲簿,導致實際白屏頁 像素占比并非100% 如圖所示粟判。因此使用floor將其尺寸大小向下取整。

遍歷快照縮略圖像素點峦剔,對白色像素(R:255 G: 255 B: 255)占比大于95%的頁面浮入,認定其為白屏。如下

- (BOOL)searchEveryPixel:(UIImage *)image {
    CGImageRef cgImage = [image CGImage];
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //每個像素點包含r g b a 四個字節(jié)
    size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
 
    CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(dataProvider);
    UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);
 
    int whiteCount = 0;
    int totalCount = 0;
 
    for (int j = 0; j < height; j ++ ) {
        for (int i = 0; i < width; i ++) {
            UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
            UInt8 red   = * pt;
            UInt8 green = *(pt + 1);
            UInt8 blue  = *(pt + 2);
//            UInt8 alpha = *(pt + 3);
 
            totalCount ++;
            if (red == 255 && green == 255 && blue == 255) {
                whiteCount ++;
            }
        }
    }
    float proportion = (float)whiteCount / totalCount ;
    NSLog(@"當前像素點數:%d,白色像素點數:%d , 占比: %f",totalCount , whiteCount , proportion );
    if (proportion > 0.95) {
        return YES;
    }else{
        return NO;
    }
} 

總結:

typedef NS_ENUM(NSUInteger,webviewLoadingStatus) {
 
    WebViewNormalStatus = 0, //正常
 
    WebViewErrorStatus, //白屏
 
    WebViewPendStatus, //待決
};

/// 判斷是否白屏
- (void)judgeLoadingStatus:(WKWebView *)webview  withBlock:(void (^)(webviewLoadingStatus status))completionBlock{
    webviewLoadingStatus __block status = WebViewPendStatus;
    if (@available(iOS 11.0, *)) {
        if (webview && [webview isKindOfClass:[WKWebView class]]) {
 
            CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //狀態(tài)欄高度
            CGFloat navigationHeight = self.navigationController.navigationBar.frame.size.height; //導航欄高度
            WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
            shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, webview.bounds.size.width, (webview.bounds.size.height - navigationHeight - statusBarHeight)); //僅截圖檢測導航欄以下部分內容
            [webview takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                if (snapshotImage) {
                    UIImage * scaleImage = [self scaleImage:snapshotImage];
                    BOOL isWhiteScreen = [self searchEveryPixel:scaleImage];
                    if (isWhiteScreen) {
                       status = WebViewErrorStatus;
                    }else{
                       status = WebViewNormalStatus;
                    }
                }
                if (completionBlock) {
                    completionBlock(status);
                }
            }];
        }
    }
}
 
/// 遍歷像素點 白色像素占比大于95%認定為白屏
- (BOOL)searchEveryPixel:(UIImage *)image {
    CGImageRef cgImage = [image CGImage];
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //每個像素點包含r g b a 四個字節(jié)
    size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
 
    CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(dataProvider);
    UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);
 
    int whiteCount = 0;
    int totalCount = 0;
 
    for (int j = 0; j < height; j ++ ) {
        for (int i = 0; i < width; i ++) {
            UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
            UInt8 red   = * pt;
            UInt8 green = *(pt + 1);
            UInt8 blue  = *(pt + 2);
 
            totalCount ++;
            if (red == 255 && green == 255 && blue == 255) {
                whiteCount ++;
            }
        }
    }
    float proportion = (float)whiteCount / totalCount ;
    NSLog(@"當前像素點數:%d,白色像素點數:%d , 占比: %f",totalCount , whiteCount , proportion );
    if (proportion > 0.95) {
        return YES;
    }else{
        return NO;
    }
}
 
///縮放圖片
- (UIImage *)scaleImage: (UIImage *)image {
    CGFloat scale = 0.2;
    CGSize newsize;
    newsize.width = floor(image.size.width * scale);
    newsize.height = floor(image.size.height * scale);
    if (@available(iOS 10.0, *)) {
        UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
          return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                        [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                 }];
    }else{
        return image;
    }
}

在頁面加載完成的代理函數中進行判斷羊异,決定是否重新進行加載,如下:

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    [self judgeLoadingStatus:webView withBlock:^(webviewLoadingStatus status) {
        if(status == WebViewNormalStatus){
            //頁面狀態(tài)正常
            [self stopIndicatorWithImmediate:NO afterDelay:1.5f indicatorString:@"頁面加載完成" complete:nil];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self.webView evaluateJavaScript:@"signjsResult()" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
                }];
            });
        }else{
            //可能發(fā)生了白屏彤断,刷新頁面
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSURLRequest *request = [[NSURLRequest alloc]initWithURL:webView.URL];
                [self.webView loadRequest:request];
            });
        }
    }];
}

經過測試野舶,發(fā)現頁面加載白屏時函數確實可以檢測到。經過排查宰衙,發(fā)現造成偶性白屏時平道,控制臺輸出內容如下 : urface creation failed for size。是由于布局問題產生了頁面白屏供炼,白屏前設置約束的代碼如下 :

- (void)viewDidLoad {
    [super viewDidLoad];
    //設置控制器標題
    self.title = self.model.menuName;
    //設置服務器URL字符串
    NSString *str;
    str =[NSString stringWithFormat:@"%@%@?access_token=%@",web_base_url,self.model.request,access_token_macro];
    NSLog(@"======:%@",str);
    str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    //設置web視圖屬性
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    WKPreferences *preference = [[WKPreferences alloc]init];
    configuration.preferences = preference;
    configuration.selectionGranularity = YES; //允許與網頁交互
    //設置web視圖
    self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
    self.webView.navigationDelegate = self;
    self.webView.UIDelegate = self;
    [self.webView.scrollView setShowsVerticalScrollIndicator:YES];
    [self.webView.scrollView setShowsHorizontalScrollIndicator:YES];
       [self.view addSubview:self.webView];
    [[self.webView configuration].userContentController addScriptMessageHandler:self name:@"signjs"];
    /* 加載服務器url的方法*/
    NSString *url = str;
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20];
    [self.webView loadRequest:request];
    
}

///設置視圖約束
- (void)updateViewConstraints {
    [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
        if (@available(iOS 11.0, *)) {
            make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
        } else {
            make.top.equalTo(self.view.mas_topMargin);
        }
        make.left.bottom.right.equalTo(self.view);
    }];
    [super updateViewConstraints];
}

改為再將WKWebView視圖添加到控制器視圖后一屋,直接設置約束窘疮,偶發(fā)性白屏問題消失,由于布局問題引發(fā)的白屏冀墨,即使重新加載頁面也不會解決的闸衫,會陷入死循環(huán)當中。如下 :

- (void)viewDidLoad {
    [super viewDidLoad];
    //設置控制器標題
    self.title = self.model.menuName;
    //設置服務器URL字符串
    NSString *str;
    str =[NSString stringWithFormat:@"%@%@?access_token=%@",web_base_url,self.model.request,access_token_macro];
    NSLog(@"======:%@",str);
    str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    //設置web視圖屬性
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    WKPreferences *preference = [[WKPreferences alloc]init];
    configuration.preferences = preference;
    configuration.selectionGranularity = YES; //允許與網頁交互
    //設置web視圖
    self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
    self.webView.navigationDelegate = self;
    self.webView.UIDelegate = self;
    [self.webView.scrollView setShowsVerticalScrollIndicator:YES];
    [self.webView.scrollView setShowsHorizontalScrollIndicator:YES];
    [[self.webView configuration].userContentController addScriptMessageHandler:self name:@"signjs"];
    [self.view addSubview:self.webView];
    [self.webView mas_makeConstraints:^(MASConstraintMaker *make) {
        if (@available(iOS 11.0, *)) {
            make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
        } else {
            make.top.equalTo(self.view.mas_topMargin);
        }
        make.left.bottom.right.equalTo(self.view);
    }];
    /* 加載服務器url的方法*/
    NSString *url = str;
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20];
    [self.webView loadRequest:request];
    
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末诽嘉,一起剝皮案震驚了整個濱河市蔚出,隨后出現的幾起案子,更是在濱河造成了極大的恐慌虫腋,老刑警劉巖骄酗,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異悦冀,居然都是意外死亡趋翻,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門盒蟆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踏烙,“玉大人,你說我怎么就攤上這事茁影≈娴郏” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵募闲,是天一觀的道長步脓。 經常有香客問我,道長浩螺,這世上最難降的妖魔是什么靴患? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮要出,結果婚禮上鸳君,老公的妹妹穿的比我還像新娘。我一直安慰自己患蹂,他們只是感情好或颊,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著传于,像睡著了一般囱挑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沼溜,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天平挑,我揣著相機與錄音,去河邊找鬼。 笑死通熄,一個胖子當著我的面吹牛唆涝,可吹牛的內容都是我干的。 我是一名探鬼主播唇辨,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼廊酣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了助泽?” 一聲冷哼從身側響起啰扛,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嗡贺,沒想到半個月后隐解,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡诫睬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年煞茫,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摄凡。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡续徽,死狀恐怖,靈堂內的尸體忽然破棺而出亲澡,到底是詐尸還是另有隱情钦扭,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布床绪,位于F島的核電站客情,受9級特大地震影響,放射性物質發(fā)生泄漏癞己。R本人自食惡果不足惜膀斋,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痹雅。 院中可真熱鬧仰担,春花似錦、人聲如沸绩社。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愉耙。三九已至项鬼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劲阎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工鸠真, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留悯仙,地道東北人龄毡。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像锡垄,于是被迫代替她去往敵國和親沦零。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內容