標籤:
轉自http://ju.outofmemory.cn/entry/18807
有時候我們在內嵌的webview中希望點擊一個連結之後,觸發iOS原生事件,而不是webview內頁面跳轉(因為webview的跳轉很生硬,而ajax+js類比則不如原生segue平滑)。
有時候我們希望在頁面內consloe.log(‘log something‘)的時候在控制台裡看到輸出,但手機裡沒有控制台,所以我們希望可以利用xcode的控制台輸出資訊。
因為iOS沒有提供API讓我們直接用html或者js來跟外部互動,所以我們必須用另外一種巧妙的辦法來實現這兩個功能。這種方法可以滿足我們兩種需求。
console.log
在html頁面中重新定義console.log:
<script>// Debugconsole = new Object();console.log = function(log) {var iframe = document.createElement("IFRAME");iframe.setAttribute("src", "ios-log:#iOS#" + log);document.documentElement.appendChild(iframe);iframe.parentNode.removeChild(iframe);iframe = null;}console.debug = console.log;console.info = console.log;console.warn = console.log;console.error = console.log;</script>
然後在需要捕獲的viewController.m中實現協議:
- (BOOL)webView: (UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ NSString *requestString = [[[request URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding];//NSLog(requestString);if ([requestString hasPrefix:@"ios-log:"]) { NSString* logString = [[requestString componentsSeparatedByString:@":#iOS#"] objectAtIndex:1]; NSLog(@"UIWebView console: %@", logString); return NO;}return YES;}
當然前提是webView需要把委託設定為當前控制器:
self.webView =[[UIWebView alloc] initWithFrame:CGRectMake(0.0f,0.0f,self.view.bounds.size.width,self.view.bounds.size.height-44)];self.webView.delegate=self;
原理很簡單,我們重新定義了console.log函數,還有console.debug,console.info,console.warn,console.error。當我們在頁面js中調用console.log的時候,就會建立一個iframe發出請求,請求的協議為ios-log:,路徑就是我們的log字串。發出請求之後,迅速把這個iframe清理掉。
這樣,在webview中我們發出了一個請求,然後就沒了。外部我們用objective-c實現了一個協議,就是webview開始發出請求之前就會調用的函數。在這個函數中我們過濾所有的請求(因為除了ios-log,還有一些“正常”的請求比如http和mailto),當首碼為ios-log的時候,我們就NSLog即可。
if最後的return NO的意思是該webview的請求被捕獲,不再請求(這個實際上不存在的頁面)。我們希望一些合法請求(比如http、mailto等)不被捕獲,所以最後if外面丟了一個return YES。
利用連結觸發情境變換
iOS原生的情境變換叫做segue,xcode為我們內建了幾種原生動畫,比如導航條總是固定在上面的push,這樣頁面前後推動的時候,導航條保持不變(當然裡面的內容可以變),內容的切換也很流暢。segue還可以在interface builder中設定動畫效果,包括全屏翻轉或者漸進等。
有一些第三方js庫可以讓我們在webview中類比這種情境變換,也就是說用css設計一個導航條放在webview中置頂,然後用js或者css3來類比push或者flip3d的效果。比如iScroll是類比頂部和底部固定的,jQtouch(這個要FQ搜尋)是類比push和flip3d效果的。
我強烈反對在原生應用中使用js庫來實現情境變換動畫,因為非常不流暢、不跟手指、動畫效果跟原生不同,還有最後一個原因,我們是可以在webview中觸發外部情境變換的,原生的!用html5製作流暢的原生app的關鍵就是能夠方便地調用原生介面的功能或者效果我們都用原生的,而不去用笨拙的方法實現。
webview中的代碼:
<a href="myapp://somepagename">一個按鈕</a>
非常簡單,myapp這個協議你可以自己隨便命名,稍後我們會在objective-c中捕獲它。
還是要實現該webview的委託controller的協議方法,如果你已經定義這個方法了(就像上面那個例子),你只需要在方法體裡加入方法體裡面的內容,否則會提醒你重複定義。
- (BOOL)webView: (UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{NSURL *url = [request URL];if ( [[url scheme] isEqualToString:@"myapp"] ){ NSString *slug = [url path]; [self performSegueWithIdentifier:@"heroSegue" sender:slug]; return NO;}return YES;}
另外我在interface builder中已經拖拽了一個新的控制器,在新的控制器跟導航控制器之間,我直接拖了一個segue,命名id為heroSegue,所以這裡可以用performSegueWithIdentifier來調用segue。
現在,還是在本controller中,我們實現另一個委託方法:
/* * 頁面轉換時觸發 */- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {if ([segue.identifier isEqualToString:@"heroSegue"]) { NSLog(@"%@",sender); [self.webView stopLoading]; YUGHeroDetailViewController *destViewController = segue.destinationViewController; destViewController.heroSlug = (NSString *)sender;}}
也就是說,發生segue變化之前,就會執行這一方法,首先判斷identifier是不是等於heroSegue,如果是,自己的webview不再載入,目標控制器(也就是即將切換過去的子頁面的控制器)中設定公有屬性heroSlug的值。
然後,我們在目標頁面的controller的H中定義:
@property (strong) NSString *heroSlug;
最後,在目標頁面中,我們定義的congroller中的M能拿到heroSlug這個參數。
NSLog(@"%@",self.heroSlug);
這樣就可以了,拿到slug之後,我們實際上就可以調用一個本地頁面,帶上slug參數,然後通過ajax的方式讀取遠程頁面或者json資料,這個就不屬於本文內容了。
如果你是新手,在做上面的這些操作的時候可能會漏掉一兩個步驟,編輯器會報錯,這時候仔細閱讀並校對你的代碼。如果實在不行,說明清楚操作和報錯資訊,再給我留言。
練習題:原生title的好處是它在字元數較短時是置中的,而字元更長一點時會偏右顯示,更長一些時顯示省略符號。那麼webview載入一個ajax資料的頁面的時候,如何在頁面載入成功時,設定原生title?
提示,還是自訂協議。
使用HTML5構建iOS原生APP