先说下这篇文章的预期目标,就是利用原生库JavaScriptCore怎么在实际应用中进行交互,例子展示怎么在前端页面调用OC代码调出系统相册和相机,OC怎么把图片传至前端页面,顺便简单写写怎么把进度条封装到webView里面
这里使用组合来写这个例子,跟着一步步来
新建一个继承于UIView的类ZSZWebView,然后加上webView和progressView,代码如下
// ZSZWebVIew.h
@interface ZSZWebVIew : UIView
@end
#import "ZSZWebVIew.h"
@interface ZSZWebVIew()
@property (nonatomic, strong) UIWebView *myWebView;
@property (nonatomic, strong) UIProgressView *progressView;
@end
暂时声明两个初始化方法方便初始化这个类
// ZSZWebVIew.h
@interface ZSZWebVIew : UIView
-(instancetype)initWithFrame:(CGRect)frame HtmlString:(NSString *)htmlString baseURL:(NSURL *)baseURL;
- (instancetype)initWithFrame:(CGRect)frame UrlString:(NSString *)urlString;
@end
这里没有提供全能初始化方法,所以我们为了避免使用者没有使用我们调用的两个初始化方法而调用其他初始化方法,直接抛出异常
@implementation ZSZWebVIew
// 避免用户使用这个初始化方法
- (instancetype)init {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"必须使用ZSZWebVIew.h文件中声明的两个方法" userInfo:nil];
}
// 避免用户使用这个初始化方法
- (instancetype)initWithFrame:(CGRect)frame {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"必须使用ZSZWebVIew.h文件中声明的两个方法" userInfo:nil];
}
// 本例中使用的初始化方法
-(instancetype)initWithFrame:(CGRect)frame HtmlString:(NSString *)htmlString baseURL:(NSURL *)baseURL{
if (self = [super initWithFrame:frame]) {
self.myWebView = [[UIWebView alloc] initWithFrame:frame];
[self.myWebView loadHTMLString:htmlString baseURL:baseURL];
self.myWebView.delegate = self;
[self addSubview:self.myWebView];
self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 2)];
[self.myWebView addSubview:self.progressView];
[self increateProgress];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame UrlString:(NSString *)urlString{
if (self = [super initWithFrame:frame]) {
self.myWebView = [[UIWebView alloc] initWithFrame:frame];
[self.myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
self.myWebView.delegate = self;
[self addSubview:self.myWebView];
self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 2)];
[self.myWebView addSubview:self.progressView];
[self increateProgress];
}
return self;
}
#pragma mark - 进度条累加 这里先慢慢的让进度条从0累加到0.8,在webView加载页面完成时隐藏
static float progressValue = 0.0f;
- (void)increateProgress
{
[self.progressView setProgress:progressValue animated:NO];
progressValue += 0.0001;
if (progressValue < 0.8) {
[self performSelector:@selector(increateProgress) withObject:nil afterDelay:0.001];
}else{
[self.progressView setProgress:0.8 animated:NO];
}
}
@end
@interface ZSZWebVIew()
#pragma mark --- webViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
// 重新显示进度条,从0开始递增
self.progressView.hidden = NO;
progressValue = 0;
[self increateProgress];
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 隐藏进度条
self.progressView.hidden = YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
}
}
上面的代码webView进度条逻辑就结束了
这里我们就要考虑一个问题,我们已经让这个自定义的View监听webView了,那外面的控制器怎么玩,很简单,写一个协议就好了跟webVIew的回调方法对应上
// ZSZWebVIew.h
#import
@protocol ZSZWebViewDelegate
@optional
- (BOOL)zszWebView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)zszWebViewDidFinishLoad:(UIWebView *)webView;
- (void)zszWebViewDidStartLoad:(UIWebView *)webView;
- (void)zszWebView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
@end
@interface ZSZWebVIew : UIView
@property (nonatomic, weak) iddelegate; //这里声明注意用weak,避免保留环
-(instancetype)initWithFrame:(CGRect)frame HtmlString:(NSString *)htmlString baseURL:(NSURL *)baseURL;
- (instancetype)initWithFrame:(CGRect)frame UrlString:(NSString *)urlString;
@end
然后几个webView代理方法里的代码就变成这样
#pragma mark --- webViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
self.progressView.hidden = NO;
progressValue = 0;
[self increateProgress];
// 用respondsToSelector:方法先判断delegate是否实现了这个方法
if ([self.delegate respondsToSelector:@selector(zszWebView:shouldStartLoadWithRequest:navigationType:)]) {
[self.delegate zszWebView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.progressView.hidden = YES;
if ([self.delegate respondsToSelector:@selector(zszWebViewDidFinishLoad:)]) {
[self.delegate zszWebViewDidFinishLoad:webView];
}
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
if ([self.delegate respondsToSelector:@selector(zszWebViewDidStartLoad:)]) {
[self.delegate zszWebViewDidStartLoad:webView];
}
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
if ([self.delegate respondsToSelector:@selector(zszWebView:didFailLoadWithError:)]) {
[self.delegate zszWebView:webView didFailLoadWithError:error];
}
}
接下来说说重头戏,怎么在页面调OC代码调出系统相册和相机
引入头文件 #import
遵循相机相册需要的两个代理@interface ZSZWebVIew()
在webViewDidFinishLoad:中加上让js调用OC的代码
- (void)webViewDidFinishLoad:(UIWebView *)webView {
self.progressView.hidden = YES;
// 创建JSContext
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 调用系统相机 iOSCamera 就是你自定义的一个js函数名
/*
举个例子
定义一个js函数在控制台打印一句话这样写
context[@"js函数名"] = ^(){
NSLog(@"在控制台打印一句话");
};
*/
context[@"iOSCamera"] = ^(){
// 调用系统相机的类
UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
// 设置选取的照片是否可编辑
pickerController.allowsEditing = YES;
// 设置相册呈现的样式
pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
// 选择完成图片或者点击取消按钮都是通过代理来操作我们所需要的逻辑过程
pickerController.delegate = self;
// 使用模态呈现相机 getCurrentViewController这个方法是用来拿到添加了这个View的控制器
[[self getCurrentViewController] presentViewController:pickerController animated:YES completion:nil];
return @"调用相机";
};
context[@"iOSPhotosAlbum"] = ^(){
// 调用系统相册的类
UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
// 设置选取的照片是否可编辑
pickerController.allowsEditing = YES;
// 设置相册呈现的样式
pickerController.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
// 选择完成图片或者点击取消按钮都是通过代理来操作我们所需要的逻辑过程
pickerController.delegate = self;
// 使用模态呈现相册
[[self getCurrentViewController] presentViewController:pickerController animated:YES completion:nil];
return @"调用相册";
};
if ([self.delegate respondsToSelector:@selector(zszWebViewDidFinishLoad:)]) {
[self.delegate zszWebViewDidFinishLoad:webView];
}
}
/** 获取当前View的控制器对象 */
-(UIViewController *)getCurrentViewController{
UIResponder *next = [self nextResponder];
do {
if ([next isKindOfClass:[UIViewController class]]) {
return (UIViewController *)next;
}
next = [next nextResponder];
} while (next != nil);
return nil;
}
下面的代理方法中会用OC的evaluateScript:方法去调用页面上的js函数并传图片的值
#pragma mark --- 拍完照或者相册选择照片后的方法
// 选择照片完成之后的代理方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
// info是所选择照片的信息
// UIImagePickerControllerEditedImage//编辑过的图片
// UIImagePickerControllerOriginalImage//原图
NSLog(@"info---%@",info);
// 刚才已经看了info中的键值对,可以从info中取出一个UIImage对象,将取出的对象压缩上传到服务器
UIImage *resultImage = [info objectForKey:@"UIImagePickerControllerEditedImage"];
// 压缩一下图片再传
NSData *imgData = UIImageJPEGRepresentation(resultImage, 0.001);
//首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)
JSContext *context=[self.myWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *encodedImageStr = [imgData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
[self removeSpaceAndNewline:encodedImageStr];
//使用模态返回到软件界面
[[self getCurrentViewController] dismissViewControllerAnimated:YES completion:nil];
// 这里传值给h5界面
NSString *imageString = [self removeSpaceAndNewline:encodedImageStr];
NSString *jsFunctStr = [NSString stringWithFormat:@"rtnCamera('%@')",imageString];
[context evaluateScript:jsFunctStr];
}
// 图片转成base64字符串需要先取出所有空格和换行符
- (NSString *)removeSpaceAndNewline:(NSString *)str
{
NSString *temp = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
temp = [temp stringByReplacingOccurrencesOfString:@"\r" withString:@""];
temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
return temp;
}
//点击取消按钮所执行的方法
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
//这是捕获点击右上角cancel按钮所触发的事件,如果我们需要在点击cancel按钮的时候做一些其他逻辑操作。就需要实现该代理方法,如果不做任何逻辑操作,就可以不实现
[[self getCurrentViewController] dismissViewControllerAnimated:YES completion:nil];
}
// 压缩图片的方法
- (NSData *)imageWithImage:(UIImage*)image
scaledToSize:(CGSize)newSize;
{
UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return UIImageJPEGRepresentation(newImage, 0.8);
}
下面是页面的代码(建一个html文件拖进工程,写上一下代码)
// 这里调用OC上的iOSCamera(js函数名)里的代码段
// 这里调用OC上的iOSPhotosAlbum里的代码段
接下来就是我们怎么使用我们这个封装的webView了,直接上代码
//
// ViewController.m
// ZSZWebView
//
// Created by 朱松泽 on 17/2/23.
// Copyright © 2017年 gdtech. All rights reserved.
//
#import "ViewController.h"
#import "ZSZWebVIew.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURL *baseURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
NSString *path = [[NSBundle mainBundle] pathForResource:@"webCamara" ofType:@"html"];
NSString *html = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
ZSZWebVIew *zszWebView = [[ZSZWebVIew alloc] initWithFrame:self.view.frame HtmlString:html baseURL:baseURL];
// ZSZWebVIew *zszWebView = [[ZSZWebVIew alloc] init]; // 用这个方法初始化就抛出异常
// ZSZWebVIew *zszWebView = [[ZSZWebVIew alloc] initWithFrame:self.view.frame]; // 用这个方法初始化也抛出异常
zszWebView.delegate = self;
[self.view addSubview:zszWebView];
}
- (BOOL)zszWebView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(@"shouldStartLoadWithRequest:执行完封装的代码才来到这里");
return YES;
}
- (void)zszWebViewDidFinishLoad:(UIWebView *)webView {
NSLog(@"zszWebViewDidFinishLoad:执行完封装的代码才来到这里");
}
-(void)zszWebView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
}
-(void)zszWebViewDidStartLoad:(UIWebView *)webView {
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
下面是本例的效果图,如图点击webView上的按钮跳转到相机或相册,并把图片传到webView的页面上
Demo从这里下载:https://github.com/ZSZ1994/ZSZWebView