UIWebView是iOS開發中常用的一個視圖控件,多數情況下,它被用來顯示HTML格式的內容。
除了HTML以外,UIWebView還支持iWork, Office等文檔格式:
- Excel (.xls)
- Keynote (.key.zip)
- Numbers (.numbers.zip)
- Pages (.pages.zip)
- PDF (.pdf)
- Powerpoint (.ppt)
- Word (.doc)
- Rich Text Format (.rtf)
- Rich Text Format Directory(.rtfd.zip)
- Keynote ‘09 (.key)
- Numbers ‘09 (.numbers)
- Pages ‘09 (.pages)
載入這些文檔的方法也和html一樣:
<pre class="brush: cpp; auto-links: true; collapse: false; first-line: 1; gutter: true; html-script: true; light: false; ruler: false; smart-tabs: true; tab-size: 2; toolbar: true;">NSString *path = [[NSBundle mainBundle] pathForResource:"test.doc" ofType:nil];NSURL *url = [NSURL fileURLWithPath:path];NSURLRequest *request = [NSURLRequest requestWithURL:url];[webView loadRequest:request];
移動設備瀏覽器的功能越來越強大,包括對HTML5、CSS3的支持,提供豐富的JavaScript API用於調用設備各種功能,使得開發出來的Web App非常接近原生App。
HTML5技術有如下優點:
- 跨平臺兼容性:不受移動平臺及設備的限制,不需要單獨針對iOS或Android平臺、不同尺寸的設備編寫特定的代碼
- 快速的開發效率、快速更新及發佈效率
- 低技術門檻及維護成本:只需要掌握HTML5/CSS/JavaScript
當然,HTML5也有它的缺點:
- 訪問設備特定功能的API非常有限:侷限於瀏覽器運行環境,可用的API遠遠少於原生應用
- 性能低於原生應用導致用戶體驗較差:特別是某一些絢麗的效果,或者是互動性很強的功能
隨着HTML5的流行,出現了許多優秀的HTML5框架,它們可以使開發變得更加簡單,進一步提高開發效率:
- jQuery Mobile:http://jquerymobile.com/
- jQTouch:http://jqtjs.com/
- Sencha Touch:http://www.sencha.com/products/touch
- NimbleKit:http://www.nimblekit.com/
- The-M-Project:http://www.the-m-project.net/
- Jo:http://joapp.com/
- DHTMLX Touch:http://dhtmlx.com/touch/
Native App需要較高的技術水平,雖然性能優越用戶體驗較好,但跨平臺兼容性差,而且開發、維護成本太高,難以適應快速更新的需求變化;而Web App技術門檻低,良好的跨平臺兼容性,開發、維護成本低,但是性能低導致用戶體驗較差。
Native App開發方法適合於遊戲等需要良好用戶體驗的應用,而Web App開發方法適合沒有太多交互的應用。這兩種方法就像兩個極端,而一般性應用並不是特別需要其中一種方法帶來的好處,於是就產生了結合這兩種開發方法的折中方案:Hybrid開發方法。
針對一般性應用,使用Hybrid開發方法,開發者就能使用跨平臺的HTML5技術開發大部分的應用程序代碼,又能在需要的時候使用一些設備的功能,充分結合了Native App開發方法和Web App開發方法的長處,在提供良好用戶體驗的同時,大大降低開發和維護的成本以及提高效率。
Hybrid開發方式也有一些框架/工具:
- Xamarin:http://xamarin.com/ 含iOS平臺的MonoTouch, Android平臺的MonoDroid,甚至還有mac桌面app
- PhoneGap:http://phonegap.com/ 它的核心Cordova已貢獻給Apache
- Sencha Architect:http://www.sencha.com/products/architect
- Titanium (js –> native):http://www.appcelerator.com/platform/titanium-platform/
- AppMobi XDK:http://www.appmobi.com/
- AppCan:http://appcan.cn/
其中,Xamarin可以採用純C#代碼開發iOS/Android應用。而PhoneGap則是針對不同平臺的WebView進行封裝和擴展,使開發人員可以通過Javascript訪問設備的一些功能。
當然,使用這些框架/工具需要一定的學習成本,如果對Objective-C和HTML5相關技術比較熟悉,也可以完全不用依賴於這些框架進行開發。
UIWebView提供了stringByEvaluatingJavaScriptFromString方法,它將Javascript代碼嵌入到頁面中運行,並將運行結果返回。
NSString *result1 = [webView stringByEvaluatingJavaScriptFromString:@"alert('lwme.cnblogs.com');"]; // 彈出提示,無返回值
NSString *result2 = [webView stringByEvaluatingJavaScriptFromString:@"location.href;"]; // 返回頁面地址
NSString *result3 = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('lwme').innerHTML;"]; // 返回頁面某個標記內容
NSString *result4 = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('lwme').innerHTML = 'lwme.cnblogs.com';"]; // 設置頁面某個標記內容
需要注意的是:
另外需要注意,運行部分腳本時需要確定頁面是否加載完成(DOMContentLoaded)。
當然,stringByEvaluatingJavaScriptFromString只是Native向UIWebView中的網頁單向的通信,UIWebView中的網頁向Native通信則需要通過UIWebView的協議webView:shouldStartLoadWithRequest:navigationType:
。
首先,創建一個文件命名爲test.html,內容如下:
<a href="js-call://test/lwme.cnblogs.com">測試</a>
<a href="js-call://other/lwme.cnblogs.com">測試2</a>
然後,在Native實現如下代碼:
@interface LwmeTestViewController ()<UIWebViewDelegate>
@end
@implementation LwmeTestViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 設置delegate並加載html
self.webView.delegate = self;
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[self.webView loadHTMLString:fileContent baseURL:nil];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *requestString = [[request URL] absoluteString];
NSString *protocol = @"js-call://";
if ([requestString hasPrefix:protocol]) {
NSString *requestContent = [requestString substringFromIndex:[protocol length]];
NSArray *vals = [requestContent componentsSeparatedByString:@"/"];
if ([vals[0] isEqualToString:@"test"]) { //test方法
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"alert('地址:%@');", vals[1]]];
}
else {
[webView stringByEvaluatingJavaScriptFromString:@"alert('未定義');"];
}
return NO; // 對於js-call://協議不執行跳轉
}
return YES;
}
這樣就完成了簡單的通信,UIWebView中的網頁需要訪問設備的功能都可以在webView:shouldStartLoadWithRequest:navigationType:
編寫相應的代碼來實現。
iOS 6以上版本的Mobile Safari支持在網頁中調用攝像頭,只需要放置以下代碼:
<input type="file" capture="camera" accept="image/*" id="cameraInput">
但是iOS 5的瀏覽器還不支持這個功能,如果需要調用攝像頭,則依然需要通過Hybrid開發方式來實現。
首先,創建一個文件命名爲camera.html,定義三個按鈕分別用於獲取攝像頭、圖庫、相冊:
<script>
function cameraCallback(imageData) {
var img = createImageWithBase64(imageData);
document.getElementById("cameraWrapper").appendChild(img);
}
function photolibraryCallback(imageData) {
var img = createImageWithBase64(imageData);
document.getElementById("photolibraryWrapper").appendChild(img);
}
function albumCallback(imageData) {
var img = createImageWithBase64(imageData);
document.getElementById("albumWrapper").appendChild(img);
}
function createImageWithBase64(imageData) {
var img = new Image();
img.src = "data:image/jpeg;base64," + imageData;
img.style.width = "50px";
img.style.height = "50px";
return img;
}
</script>
<p style="text-align:center;padding:20px;">
<a href="js-call://camera/cameraCallback">拍照</a>
<a href="js-call://photolibrary/photolibraryCallback">圖庫</a>
<a href="js-call://album/albumCallback">相冊</a>
</p>
<fieldset>
<legend>拍照</legend>
<div id="cameraWrapper">
</div>
</fieldset>
<fieldset>
<legend>圖庫</legend>
<div id="photolibraryWrapper">
</div>
</fieldset>
<fieldset>
<legend>相冊</legend>
<div id="albumWrapper">
</div>
</fieldset>
Native實現代碼如下:
#import "LwmeViewController.h"
#import "NSData+Base64.h"
// Base64代碼從 http://svn.cocoasourcecode.com/MGTwitterEngine/NSData+Base64.h 和 http://svn.cocoasourcecode.com/MGTwitterEngine/NSData+Base64.m 獲取
@interface LwmeViewController ()<UIWebViewDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
{
NSString *callback; // 定義變量用於保存返回函數
}
@end
@implementation LwmeViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 設置delegate並載入html文件
self.webView.delegate = self;
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"camera" ofType:@"html"];
NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[self.webView loadHTMLString:fileContent baseURL:nil];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *requestString = [[request URL] absoluteString];
NSString *protocol = @"js-call://"; //協議名稱
if ([requestString hasPrefix:protocol]) {
NSString *requestContent = [requestString substringFromIndex:[protocol length]];
NSArray *vals = [requestContent componentsSeparatedByString:@"/"];
if ([[vals objectAtIndex:0] isEqualToString:@"camera"]) { // 攝像頭
callback = [vals objectAtIndex:1];
[self doAction:UIImagePickerControllerSourceTypeCamera];
} else if([[vals objectAtIndex:0] isEqualToString:@"photolibrary"]) { // 圖庫
callback = [vals objectAtIndex:1];
[self doAction:UIImagePickerControllerSourceTypePhotoLibrary];
} else if([[vals objectAtIndex:0] isEqualToString:@"album"]) { // 相冊
callback = [vals objectAtIndex:1];
[self doAction:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
}
else {
[webView stringByEvaluatingJavaScriptFromString:@"alert('未定義/lwme.cnblogs.com');"];
}
return NO;
}
return YES;
}
- (void)doAction:(UIImagePickerControllerSourceType)sourceType
{
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
imagePicker.sourceType = sourceType;
} else {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"照片獲取失敗" message:@"沒有可用的照片來源" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
[av show];
return;
}
// iPad設備做額外處理
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
[popover presentPopoverFromRect:CGRectMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 3, 10, 10) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
} else {
[self presentModalViewController:imagePicker animated:YES];
}
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
if ([[info objectForKey:UIImagePickerControllerMediaType] isEqualToString:@"public.image"]) {
// 返回圖片
UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
// 設置並顯示加載動畫
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"正在處理圖片..." message:@"\n\n"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil, nil];
UIActivityIndicatorView *loading = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
loading.center = CGPointMake(139.5, 75.5);
[av addSubview:loading];
[loading startAnimating];
[av show];
// 在後臺線程處理圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// 這裏可以對圖片做一些處理,如調整大小等,否則圖片過大顯示在網頁上時會造成內存警告
NSString *base64 = [UIImagePNGRepresentation(originalImage, 0.3) base64Encoding]; // 圖片轉換成base64字符串
[self performSelectorOnMainThread:@selector(doCallback:) withObject:base64 waitUntilDone:YES]; // 把結果顯示在網頁上
[av dismissWithClickedButtonIndex:0 animated:YES]; // 關閉動畫
});
}
[picker dismissModalViewControllerAnimated:YES];
}
- (void)doCallback:(NSString *)data
{
[self.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"%@('%@');", callback, data]];
}
@end
以上簡單的代碼雖然比較粗糙,但也基本實現了功能,如果有更多的需求,可以在這個基礎上進行一些封裝、擴展。
源代碼提供在GitHub:https://github.com/corminlu/UIWebViewCallCamera
當然,這方面也有一些封裝的比較好的類庫:
- WebViewJavascriptBridge:https://github.com/marcuswestin/WebViewJavascriptBridge
- GAJavaScript:https://github.com/newyankeecodeshop/GAJavaScript
- NativeBridge:https://github.com/ochameau/NativeBridge (他的文章有更詳細的說明)