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

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
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技术及框架

移动设备浏览器的功能越来越强大,包括对HTML5、CSS3的支持,提供丰富的JavaScript API用于调用设备各种功能,使得开发出来的Web App非常接近原生App。

HTML5技术有如下优点:

  • 跨平台兼容性:不受移动平台及设备的限制,不需要单独针对iOS或Android平台、不同尺寸的设备编写特定的代码
  • 快速的开发效率、快速更新及发布效率
  • 低技术门槛及维护成本:只需要掌握HTML5/CSS/JavaScript

当然,HTML5也有它的缺点:

  • 访问设备特定功能的API非常有限:局限于浏览器运行环境,可用的API远远少于原生应用
  • 性能低于原生应用导致用户体验较差:特别是某一些绚丽的效果,或者是互动性很强的功能

随着HTML5的流行,出现了许多优秀的HTML5框架,它们可以使开发变得更加简单,进一步提高开发效率:

 

Hybrid开发方式

Native App需要较高的技术水平,虽然性能优越用户体验较好,但跨平台兼容性差,而且开发、维护成本太高,难以适应快速更新的需求变化;而Web App技术门槛低,良好的跨平台兼容性,开发、维护成本低,但是性能低导致用户体验较差。

Native App开发方法适合于游戏等需要良好用户体验的应用,而Web App开发方法适合没有太多交互的应用。这两种方法就像两个极端,而一般性应用并不是特别需要其中一种方法带来的好处,于是就产生了结合这两种开发方法的折中方案:Hybrid开发方法。

针对一般性应用,使用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,内容如下:

?
1
2
< a  href = "js-call://test/lwme.cnblogs.com" >测试</ a >
< a  href = "js-call://other/lwme.cnblogs.com" >测试2</ a >

然后,在Native实现如下代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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支持在网页中调用摄像头,只需要放置以下代码:

?
1
< input  type = "file"  capture = "camera"  accept = "image/*"  id = "cameraInput" >

但是iOS 5的浏览器还不支持这个功能,如果需要调用摄像头,则依然需要通过Hybrid开发方式来实现。

首先,创建一个文件命名为camera.html,定义三个按钮分别用于获取摄像头、图库、相册:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
< 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实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#import "LwmeViewController.h"
#import "NSData+Base64.h"
 
@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

当然,这方面也有一些封装的比较好的类库:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值