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框架,它们可以使开发变得更加简单,进一步提高开发效率:
- 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
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
|
|
然后,在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]; 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 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;"> </ 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" //
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]; 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:@ "
" 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 (他的文章有更详细的说明)