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)的窖壕。
//為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;
屬性描述 :只讀屬性撤蚊, 一個布爾值钝计,指示“后退”列表中是否有可導航到的“后退”項缺厉。通常用于后退前進行判斷嗜价。
- (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導航墅茉。
首先需要創(chuàng)建一個Html文件熏版,如圖:
極其簡單的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
看到頁面就是階段性的勝利:
- (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實例作為參數傳入。
//初始化一個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ī)則列表砸狞。
這里我們可以進階了,給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函數呢:
正所謂來而不往非禮也朽寞,既然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
效果如圖 :
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);
- (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 : 在警報面板被取消后調用的完成處理程序暑塑。
- (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竿音。
- (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褒翰。
- (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
運行:
關于加載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同一頁面下):
縮放前白屏檢測:
耗時20ms锦溪。
縮放后白屏檢測:
耗時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];
}