隨說 : 最近有個需求,是將公司的一個內網的頁面嵌套在app中作為一個模塊.這不是很簡單的webView請求一下就行了麼?其實內裡大有乾坤.自己也將思路整理一遍
就這樣就已經整整個baidu的頁面展示到app上
下面我們看一下webView的屬性與方法
UIWebView *webView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.view = webView; NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [webView loadRequest:request];
// 代理屬性 重點需要知道代理方法的使用 @property (nullable, nonatomic, assign) iddelegate; // 這個是webView內部的scrollView 只讀,但是利用這個屬性,設置scrollView的代理,就可以控制整個webView的滾動事件 @property(nonatomic, readonly, strong) UIScrollView *scrollView; // webView的請求,這個屬性一般在整個加載完成後才能拿到 @property (nullable, nonatomic, readonly, strong) NSURLRequest *request; // A Boolean value indicating whether the receiver can move backward. (read-only) // If YES, able to move backward; otherwise, NO. // 如果這個屬性為YES,才能後退 @property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack; // A Boolean value indicating whether the receiver can move forward. (read-only) // If YES, able to move forward; otherwise, NO. // 如果這個屬性為YES,才能前進 @property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward; // A Boolean value indicating whether the receiver is done loading content. (read-only) // If YES, the receiver is still loading content; otherwise, NO. // 這個屬性很好用,如果為YES證明webView還在加載數據,所有數據加載完畢後,webView就會為No @property (nonatomic, readonly, getter=isLoading) BOOL loading; //A Boolean value determining whether the webpage scales to fit the view and the user can change the scale. //If YES, the webpage is scaled to fit and the user can zoom in and zoom out. If NO, user zooming is disabled. The default value is NO. // YES代表網頁可以縮放,NO代表不可以縮放 @property (nonatomic) BOOL scalesPageToFit; // 設置某些數據變為鏈接形式,這個枚舉可以設置如電話號,地址,郵箱等轉化為鏈接 @property (nonatomic) UIDataDetectorTypes dataDetectorTypes NS_AVAILABLE_IOS(3_0); // iPhone Safari defaults to NO. iPad Safari defaults to YES // 設置是否使用內聯播放器播放視頻 @property (nonatomic) BOOL allowsInlineMediaPlayback NS_AVAILABLE_IOS(4_0); // iPhone and iPad Safari both default to YES // 設置視頻是否自動播放 @property (nonatomic) BOOL mediaPlaybackRequiresUserAction NS_AVAILABLE_IOS(4_0); // iPhone and iPad Safari both default to YES // 設置音頻播放是否支持ari play功能 @property (nonatomic) BOOL mediaPlaybackAllowsAirPlay NS_AVAILABLE_IOS(5_0); // iPhone and iPad Safari both default to NO // 設置是否將數據加載入內存後渲染界面 @property (nonatomic) BOOL suppressesIncrementalRendering NS_AVAILABLE_IOS(6_0); // default is YES // 設置用戶是否能打開keyboard交互 @property (nonatomic) BOOL keyboardDisplayRequiresUserAction NS_AVAILABLE_IOS(6_0); /* IOS7 */ 以後的新特性 // 這個屬性用來設置一種模式,當網頁的大小超出view時,將網頁以翻頁的效果展示,枚舉如下: @property (nonatomic) UIWebPaginationMode paginationMode NS_AVAILABLE_IOS(7_0); typedef NS_ENUM(NSInteger, UIWebPaginationMode) { UIWebPaginationModeUnpaginated, //不使用翻頁效果 UIWebPaginationModeLeftToRight, //將網頁超出部分分頁,從左向右進行翻頁 UIWebPaginationModeTopToBottom, //將網頁超出部分分頁,從上向下進行翻頁 UIWebPaginationModeBottomToTop, //將網頁超出部分分頁,從下向上進行翻頁 UIWebPaginationModeRightToLeft //將網頁超出部分分頁,從右向左進行翻頁 }; // This property determines whether certain CSS properties regarding column- and page-breaking are honored or ignored. // 這個屬性決定CSS的屬性分頁是可用還是忽略。默認是UIWebPaginationBreakingModePage @property (nonatomic) UIWebPaginationBreakingMode paginationBreakingMode NS_AVAILABLE_IOS(7_0); // 設置每一頁的長度 @property (nonatomic) CGFloat pageLength NS_AVAILABLE_IOS(7_0); // 設置每一頁的間距 @property (nonatomic) CGFloat gapBetweenPages NS_AVAILABLE_IOS(7_0); // 獲取頁數 @property (nonatomic, readonly) NSUInteger pageCount NS_AVAILABLE_IOS(7_0);
還有一些屬性請詳細翻蘋果文檔
UIWebView的代理方法是用的最多的方法,並且一般來說,相對Web頁面作處理都在這相應的4個方法中
分別解釋一下方法的調用情況
// Sent before a web view begins loading a frame.請求發送前都會調用該方法,返回NO則不處理這個請求 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; // Sent after a web view starts loading a frame. 請求發送之後開始接收響應之前會調用這個方法 - (void)webViewDidStartLoad:(UIWebView *)webView; // Sent after a web view finishes loading a frame. 請求發送之後,並且服務器已經返回響應之後調用該方法 - (void)webViewDidFinishLoad:(UIWebView *)webView; // Sent if a web view failed to load a frame. 網頁請求失敗則會調用該方法 - (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;
// 加載Data數據創建一個webView - (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL // 加載本地HTML創建一個webView - (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL // 加載一個請求創建一個webView - (void)loadRequest:(NSURLRequest *)request // 刷新網頁 - (void)reload; // 停止網頁加載內容 - (void)stopLoading; // 後退 - (void)goBack; // 前進 - (void)goForward; // 執行JS方法 - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script
從文檔中可以看到,這個是IOS8之後新增的一個類,也是蘋果推崇的一個新的類
其實和UIWebView的用法沒什麼區別
但是WKWebView相對於UIWebView強大了很多,內存的消耗相對少了,所提供的接口也豐富了。
推薦使用
多了一部操作就是需要包含webkit框架
@import webkit
WKWebView *webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.view = webView; NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [webView loadRequest:request];
// UIWebView 中會自動保存Cookie,如果登錄了一次下次再次進入的時候,會記住登錄狀態 // 在WKWebView中,新增一個configuration屬性, configuration 讓WKWebView知道登錄狀態, // configuration 可以通過已有的Cookie進行設置,也可以通過保存上一次的configuration進行設置 // WKWebViewConfiguration類中也有一些相應的屬性 @property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration; // The methods of the WKNavigationDelegate protocol help you track the progress of the web site's main frame navigations and decide load policy for main frame and subframe navigations. // WKWebView中,加入了網站導航的概念,這個對象決定主框架導航加載方法協議。 @property (nullable, nonatomic, weak) idnavigationDelegate; // The WKUIDelegate class provides methods for presenting native user interface elements on behalf of a webpage. // WKWebView中,加入了網站窗口的概念,這個對象決了webView窗口的一些方法協議。 @property (nullable, nonatomic, weak) id UIDelegate; A WKBackForwardList object is a list of webpages previously visited in a web view that can be reached by going back or forward. // WKWebView中,加入了網站列表的概念,這個WEBBackForwardList對象是以前在Web視圖訪問的網頁,可以通過去後退或前進 @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
還有很多方法,同樣可以查文檔看到
有一些方法和UIWebView是基本一直的,但是因為返回了navigation,所能用到的屬性多了很多,另外多了一些方法,將請求與相應的整個過程
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{ NSLog(@"webViewWebContentProcessDidTerminate: 當Web視圖的網頁內容被終止時調用。"); } - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation { [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; NSLog(@"webView:didFinishNavigation: 響應渲染完成後調用該方法 webView : %@ -- navigation : %@ \n\n",webView,navigation); } - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation { [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; NSLog(@"webView:didStartProvisionalNavigation: 開始請求 \n\n"); } - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation { NSLog(@"webView:didCommitNavigation: 響應的內容到達主頁面的時候響應,剛准備開始渲染頁面應用 \n\n"); } // error - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { // 類似 UIWebView 的- webView:didFailLoadWithError: NSLog(@"webView:didFailProvisionalNavigation:withError: 啟動時加載數據發生錯誤就會調用這個方法。 \n\n"); } - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{ NSLog(@"webView:didFailNavigation: 當一個正在提交的頁面在跳轉過程中出現錯誤時調用這個方法。 \n\n"); } - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ NSLog(@"請求前會先進入這個方法 webView:decidePolicyForNavigationActiondecisionHandler: %@ \n\n ",navigationAction.request); decisionHandler(WKNavigationActionPolicyAllow); } - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{ NSLog(@"返回響應前先會調用這個方法 並且已經能接收到響應webView:decidePolicyForNavigationResponse:decisionHandler: Response?%@ \n\n",navigationResponse.response); decisionHandler(WKNavigationResponsePolicyAllow); } - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{ NSLog(@"webView:didReceiveServerRedirectForProvisionalNavigation: 重定向的時候就會調用 \n\n"); }
這些方法,基本上和UIWebView中的使用用法是一致的,所以
// 這是加載網頁最常用的一種方式,通過一個網頁URL來加載一個WKWebView,這個URL可以是遠程的也可以是本地的,例如我加載百度的主頁 - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request; // 根據一個文件,加載一個WKWebView - (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL NS_AVAILABLE(10_11, 9_0); // 這個方法需要將html文件讀取為字符串從而加載為WKWebView,其中baseURL是我們自己設置的一個路徑,用於尋找html文件中引用的圖片等素材。 - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL; // 這個方式使用的比較少,但也更加自由,其中data是文件數據,MIMEType是文件類型,characterEncodingName是編碼類型,baseURL是素材資源路徑 - (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0);
下面會總結一些我在開發過程中遇到的坑,和解決問題的一些思路,不過在此之前我發現,如果要webView玩得好,有以下幾點的只是也需要掌握好,因為我認為在H5崛起的今天,源生App和H5的交互之間會產生比較大改變,而且源生與H5之間的混編,越來越被重視.所以 :
源生技術,特別是有關於webView這一塊的API要非常熟練,js語法, js的語法需要熟練,特別是操作document的幾個常用js,標簽需要用得滾瓜爛熟.要非常了解網絡請求 - 響應的機制,理解請求頭,響應頭,等等.HTTP的整套協議首先看看百度的頁面,這是用Chrome浏覽器打開的開發者模式
基本界面組成如下,基本使用用法請詳情百度,這裡不作介紹
假設現在想將這個Logo由網頁開始加載就去掉
百度的logo就是一個div套著一個image標簽
- (void)webViewDidFinishLoad:(UIWebView *)webView { // 在HTML標簽都加載完成後,開始處理HTML標簽,調用JS,操作document [webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('plus-card').remove();"]; }
就這樣, logo標簽就被去掉了,思路就是等HTML加載完成後,操作JS從而操作document標簽從而改變整個html頁面的應用,下圖是去掉整個Body主題內容後的結果
另外還可以將一段函數封裝到裡面,執行函數,原理是通過stringByEvaluatingJavaScriptFromString將JS函數寫進head標簽中,然後再調用該函數
// 自定義editMyLogo函數 [webView stringByEvaluatingJavaScriptFromString:@"var script = document.createElement('script');" "script.type = 'text/javascript';" "script.text = \"function editMyLogo() { " "var logo = document.getElementById('logo');" "logo.innerHTML= logo.innerHTML + '這是我自己定義的名字';" "var imglist = logo.getElementsByTagName('IMG');" "for (i=0 ; i < imglist.length ; i++ ){" "imglist[i].src = 'http://pic.to8to.com/attch/day_160218/20160218_d968438a2434b62ba59dH7q5KEzTS6OH.png';" "}" "}\";" "document.getElementsByTagName('head')[0].appendChild(script);"]; // 執行editMyLogo函數 [webView stringByEvaluatingJavaScriptFromString:@"editMyLogo();"];
效果如下 :
有幾點問題,這種操作是在webViewDidFinishLoad方法下進行的,webViewDidFinishLoad方法是webView的document已經渲染好後,再去處理這個這個頁面.
你會發現有時候會出現一些閃屏現象,原因是渲染過後,內部處理JS代碼後,頁面會再渲染一次資源浪費,假設這邊的需求只需要顯示10%的內容,卻要加載100%的內容,不過這一方面還需要網頁端作出很好的適配某些時候,JS會失效,不知道什麼原因,有些時候自定義加載的JS的方法並沒有執行到.等於內容並沒有屏蔽等等..@property (nonatomic, assign) BOOL isPost; // 定義一個變量 // 每一個請求開始發送前都會調用這個方法 // 1, 定義一個全局變量currentRequest,用作保存當前的請求 // 2, 將請求轉換成data,然後處理data再將data作為請求數據再次請求 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if (!_isPost) { NSHTTPURLResponse *response = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil]; if (response.statusCode == 404) { // 這裡處理 404 代碼 } else if (response.statusCode == 403) { // 這裡處理 403 代碼 } else { _isPost = true; [webView loadData:data MIMEType:@"text/html" textEncodingName:@"NSUTF8StringEncoding" baseURL:[request URL]]; } return NO; }else{ NSLog(@"\n\n shouldStartLoadWithRequest請求准備 -- %@ \n\n ",request); _isPost = NO; return YES; } }
在處理HTML這裡,將你想隱藏的頁面,加上 display:none 屬性,
或者,將整段HTML標簽去掉.