iOS UIWebView中javascript与Objective-C交互、获取摄像头

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一样:

  1. NSString *path = [[NSBundle mainBundle] pathForResource:"test.doc" ofType:nil];
  2. NSURL *url = [NSURL fileURLWithPath:path];
  3. NSURLRequest *request = [NSURLRequest requestWithURL:url];
  4. [webView loadRequest:request];
复制代码
HTML5技术及框架移动设备浏览器的功能越来越强大,包括对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/
Hybrid开发方式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与Javascript交互UIWebView提供了stringByEvaluatingJavaScriptFromString方法,它将Javascript代码嵌入到页面中运行,并将运行结果返回。
  1. NSString *result1 = [webView stringByEvaluatingJavaScriptFromString:@"alert('lwme.cnblogs.com');"]; // 弹出提示,无返回值
  2. NSString *result2 = [webView stringByEvaluatingJavaScriptFromString:@"location.href;"]; // 返回页面地址
  3. NSString *result3 = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('lwme').innerHTML;"]; // 返回页面某个标记内容
  4. 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,内容如下:
  1. <a href="js-call://test/lwme.cnblogs.com">测试</a>
  2. <a href="js-call://other/lwme.cnblogs.com">测试2</a>
复制代码
然后,在Native实现如下代码:
  1. @interface LwmeTestViewController ()<UIWebViewDelegate>
  2. @end

  3. @implementation LwmeTestViewController
  4. - (void)viewDidLoad
  5. {
  6.     [super viewDidLoad];
  7.     // 设置delegate并加载html
  8.     self.webView.delegate = self;
  9.     NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
  10.     NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
  11.     [self.webView loadHTMLString:fileContent baseURL:nil];
  12. }
  13. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
  14. {
  15.     NSString *requestString = [[request URL] absoluteString];
  16.     NSString *protocol = @"js-call://";
  17.     if ([requestString hasPrefix:protocol]) {
  18.         NSString *requestContent = [requestString substringFromIndex:[protocol length]];
  19.         NSArray *vals = [requestContent componentsSeparatedByString:@"/"];
  20.         if ([vals[0] isEqualToString:@"test"]) { //test方法
  21.             [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"alert('地址:%@');", vals[1]]];
  22.         }
  23.         else {
  24.             [webView stringByEvaluatingJavaScriptFromString:@"alert('未定义');"];
  25.         }
  26.         return NO; // 对于js-call://协议不执行跳转
  27.     }
  28.     return YES;
  29. }
复制代码
这样就完成了简单的通信,UIWebView中的网页需要访问设备的功能都可以在 webView:shouldStartLoadWithRequest:navigationType:编写相应的代码来实现。 在UIWebView中调用摄像头、相册、图库iOS 6以上版本的Mobile Safari支持在网页中调用摄像头,只需要放置以下代码:
  1. <input type="file" capture="camera" accept="image/*" id="cameraInput">
复制代码
但是iOS 5的浏览器还不支持这个功能,如果需要调用摄像头,则依然需要通过Hybrid开发方式来实现。首先,创建一个文件命名为camera.html,定义三个按钮分别用于获取摄像头、图库、相册:
  1. <script>
  2.     function cameraCallback(imageData) {
  3.         var img = createImageWithBase64(imageData);
  4.         document.getElementById("cameraWrapper").appendChild(img);
  5.     }
  6.     function photolibraryCallback(imageData) {
  7.         var img = createImageWithBase64(imageData);
  8.         document.getElementById("photolibraryWrapper").appendChild(img);
  9.     }
  10.     function albumCallback(imageData) {
  11.         var img = createImageWithBase64(imageData);
  12.         document.getElementById("albumWrapper").appendChild(img);
  13.     }
  14.     function createImageWithBase64(imageData) {
  15.         var img = new Image();
  16.         img.src = "data:image/jpeg;base64," + imageData;
  17.         img.style.width = "50px";
  18.         img.style.height = "50px";
  19.         return img;
  20.     }
  21. </script>
  22. <p style="text-align:center;padding:20px;">
  23.     <a href="js-call://camera/cameraCallback">拍照</a>  
  24.     <a href="js-call://photolibrary/photolibraryCallback">图库</a>  
  25.     <a href="js-call://album/albumCallback">相册</a>
  26. </p>

  27. <fieldset>
  28.     <legend>拍照</legend>
  29.     <div id="cameraWrapper">
  30.     </div>
  31. </fieldset>

  32. <fieldset>
  33.     <legend>图库</legend>
  34.     <div id="photolibraryWrapper">
  35.     </div>
  36. </fieldset>

  37. <fieldset>
  38.     <legend>相册</legend>
  39.     <div id="albumWrapper">
  40.     </div>
  41. </fieldset>
复制代码
Native实现代码如下:
  1. #import "LwmeViewController.h"
  2. #import "NSData+Base64.h"
  3. // Base64代码从 http://svn.cocoasourcecode.com/MGTwitterEngine/NSData+Base64.h 和 http://svn.cocoasourcecode.com/MGTwitterEngine/NSData+Base64.m 获取

  4. @interface LwmeViewController ()<UIWebViewDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
  5. {
  6.     NSString *callback; // 定义变量用于保存返回函数
  7. }
  8. @end

  9. @implementation LwmeViewController
  10. - (void)viewDidLoad
  11. {
  12.     [super viewDidLoad];
  13.     // 设置delegate并载入html文件
  14.     self.webView.delegate = self;
  15.     NSString *filePath = [[NSBundle mainBundle] pathForResource:@"camera" ofType:@"html"];
  16.     NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
  17.     [self.webView loadHTMLString:fileContent baseURL:nil];
  18. }

  19. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
  20. {
  21.     NSString *requestString = [[request URL] absoluteString];
  22.     NSString *protocol = @"js-call://"; //协议名称
  23.     if ([requestString hasPrefix:protocol]) {
  24.         NSString *requestContent = [requestString substringFromIndex:[protocol length]];
  25.         NSArray *vals = [requestContent componentsSeparatedByString:@"/"];
  26.         if ([[vals objectAtIndex:0] isEqualToString:@"camera"]) { // 摄像头
  27.             callback = [vals objectAtIndex:1];
  28.             [self doAction:UIImagePickerControllerSourceTypeCamera];
  29.         } else if([[vals objectAtIndex:0] isEqualToString:@"photolibrary"]) { // 图库
  30.             callback = [vals objectAtIndex:1];
  31.             [self doAction:UIImagePickerControllerSourceTypePhotoLibrary];
  32.         } else if([[vals objectAtIndex:0] isEqualToString:@"album"]) { // 相册
  33.             callback = [vals objectAtIndex:1];
  34.             [self doAction:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
  35.         }
  36.         else {
  37.             [webView stringByEvaluatingJavaScriptFromString:@"alert('未定义/lwme.cnblogs.com');"];
  38.         }
  39.         return NO;
  40.     }
  41.     return YES;
  42. }

  43. - (void)doAction:(UIImagePickerControllerSourceType)sourceType
  44. {
  45.     UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
  46.     imagePicker.delegate = self;
  47.     if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
  48.         imagePicker.sourceType = sourceType;
  49.     } else {
  50.         UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"照片获取失败" message:@"没有可用的照片来源" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
  51.         [av show];
  52.         return;
  53.     }
  54.     // iPad设备做额外处理
  55.     if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
  56.         UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
  57.         [popover presentPopoverFromRect:CGRectMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 3, 10, 10) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
  58.     } else {
  59.         [self presentModalViewController:imagePicker animated:YES];
  60.     }
  61. }

  62. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
  63. {
  64.     if ([[info objectForKey:UIImagePickerControllerMediaType] isEqualToString:@"public.image"]) {
  65.         // 返回图片
  66.         UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
  67.         // 设置并显示加载动画
  68.         UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"正在处理图片..." message:@"\n\n"
  69.                                                 delegate:self
  70.                                        cancelButtonTitle:nil
  71.                                        otherButtonTitles:nil, nil];
  72.          
  73.         UIActivityIndicatorView *loading = [[UIActivityIndicatorView alloc]
  74.                                             initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
  75.         loading.center = CGPointMake(139.5, 75.5);
  76.         [av addSubview:loading];
  77.         [loading startAnimating];
  78.         [av show];
  79.         // 在后台线程处理图片
  80.         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  81.             // 这里可以对图片做一些处理,如调整大小等,否则图片过大显示在网页上时会造成内存警告
  82.             NSString *base64 = [UIImagePNGRepresentation(originalImage, 0.3) base64Encoding]; // 图片转换成base64字符串
  83.             [self performSelectorOnMainThread:@selector(doCallback:) withObject:base64 waitUntilDone:YES]; // 把结果显示在网页上
  84.             [av dismissWithClickedButtonIndex:0 animated:YES]; // 关闭动画
  85.         });
  86.     }
  87.      
  88.     [picker dismissModalViewControllerAnimated:YES];
  89. }

  90. - (void)doCallback:(NSString *)data
  91. {
  92.     [self.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"%@('%@');", callback, data]];
  93. }
  94. @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  (他的文章有更详细的说明)


你可能感兴趣的:(iOS UIWebView中javascript与Objective-C交互、获取摄像头)