您的位置:首页 > 移动开发 > Objective-C

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

2014-05-08 23:36 513 查看
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一样:

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代码嵌入到页面中运行,并将运行结果返回。

需要注意的是:

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>  
<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.hhttp://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 (他的文章有更详细的说明)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: