UIWebView中javascript與Objective-C交互、獲取攝像頭

UIWebView是iOS開發中經常使用的一個視圖控件,多數狀況下,它被用來顯示HTML格式的內容。html

支持的文檔格式

除了HTML之外,UIWebView還支持iWork, Office等文檔格式:jquery

  • 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同樣:ios

1
2
3
4
NSString *path = [[NSBundle mainBundle] pathForResource: "test.doc"  ofType:nil];
NSURL *url = [NSURL fileURLWithPath:path];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];

 

HTML5技術及框架

移動設備瀏覽器的功能愈來愈強大,包括對HTML五、CSS3的支持,提供豐富的JavaScript API用於調用設備各類功能,使得開發出來的Web App很是接近原生App。git

HTML5技術有以下優勢:github

  • 跨平臺兼容性:不受移動平臺及設備的限制,不須要單獨針對iOS或Android平臺、不一樣尺寸的設備編寫特定的代碼
  • 快速的開發效率、快速更新及發佈效率
  • 低技術門檻及維護成本:只須要掌握HTML5/CSS/JavaScript

固然,HTML5也有它的缺點:web

  • 訪問設備特定功能的API很是有限:侷限於瀏覽器運行環境,可用的API遠遠少於原生應用
  • 性能低於原生應用致使用戶體驗較差:特別是某一些絢麗的效果,或者是互動性很強的功能

隨着HTML5的流行,出現了許多優秀的HTML5框架,它們可使開發變得更加簡單,進一步提升開發效率:apache

 

Hybrid開發方式

Native App須要較高的技術水平,雖然性能優越用戶體驗較好,但跨平臺兼容性差,並且開發、維護成本過高,難以適應快速更新的需求變化;而Web App技術門檻低,良好的跨平臺兼容性,開發、維護成本低,可是性能低致使用戶體驗較差。瀏覽器

Native App開發方法適合於遊戲等須要良好用戶體驗的應用,而Web App開發方法適合沒有太多交互的應用。這兩種方法就像兩個極端,而通常性應用並非特別須要其中一種方法帶來的好處,因而就產生告終合這兩種開發方法的折中方案:Hybrid開發方法。app

針對通常性應用,使用Hybrid開發方法,開發者就能使用跨平臺的HTML5技術開發大部分的應用程序代碼,又能在須要的時候使用一些設備的功能,充分結合了Native App開發方法和Web App開發方法的長處,在提供良好用戶體驗的同時,大大下降開發和維護的成本以及提升效率。框架

Hybrid開發方式也有一些框架/工具:

其中,Xamarin能夠採用純C#代碼開發iOS/Android應用。而PhoneGap則是針對不一樣平臺的WebView進行封裝和擴展,使開發人員能夠經過Javascript訪問設備的一些功能。

固然,使用這些框架/工具須要必定的學習成本,若是對Objective-C和HTML5相關技術比較熟悉,也能夠徹底不用依賴於這些框架進行開發。

 

UIWebView與Javascript交互

UIWebView提供了stringByEvaluatingJavaScriptFromString方法,它將Javascript代碼嵌入到頁面中運行,並將運行結果返回。

1
2
3
4
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';" ]; // 設置頁面某個標記內容

須要注意的是:

  • js的執行時間不能超過10秒,不然UIWebView將中止執行腳本。
  • js分配的內存限制爲10M,若是超過此限制,UIWebView將引起異常。

另外須要注意,運行部分腳本時須要肯定頁面是否加載完成(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:編寫相應的代碼來實現。

 

在UIWebView中調用攝像頭、相冊、圖庫

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>&nbsp;&nbsp;
    <a href="js-call://photolibrary/photolibraryCallback">圖庫</a>&nbsp;&nbsp;
    <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

固然,這方面也有一些封裝的比較好的類庫:

相關文章
相關標籤/搜索