先說下這篇文章的預期目標,就是利用原生庫JavaScriptCore怎么在實際應用中進行交互,例子展示怎么在前端頁面調用OC代碼調出系統相冊和相機,OC怎么把圖片傳至前端頁面,順便簡單寫寫怎么把進度條封裝到webView里面
這里使用組合來寫這個例子,跟著一步步來
新建一個繼承于UIView的類ZSZWebView,然后加上webView和progressView,代碼如下
// ZSZWebVIew.h
@interface ZSZWebVIew : UIView
@end
#import "ZSZWebVIew.h"
@interface ZSZWebVIew()<UIWebViewDelegate>
@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()<UIWebViewDelegate>
#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 <UIKit/UIKit.h>
@protocol ZSZWebViewDelegate <NSObject>
@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) id<ZSZWebViewDelegate>delegate; //這里聲明注意用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 <JavaScriptCore/JavaScriptCore.h>
遵循相機相冊需要的兩個代理@interface ZSZWebVIew()<UIWebViewDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate>
在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<NSString *,id> *)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文件拖進工程,寫上一下代碼)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
// 這里調用OC上的iOSCamera(js函數名)里的代碼段
<button onclick="iOSCamera()" style="width: 80px;height: 35px;">系統相機</button>
// 這里調用OC上的iOSPhotosAlbum里的代碼段
<button onclick="iOSPhotosAlbum()" style="width: 80px;height: 35px;">系統相冊</button>
<div id='zsz'></div>
</body>
<script type="application/javascript">
// 這個js函數在OC被調用
function rtnCamera(basedata) {
var zsz=document.getElementById('zsz');
zsz.innerHTML="<image style='width:200px;height:200px;' src='data:image/png;base64,"+basedata+"'>";
};
</script>
</html>
接下來就是我們怎么使用我們這個封裝的webView了,直接上代碼
//
// ViewController.m
// ZSZWebView
//
// Created by 朱松澤 on 17/2/23.
// Copyright ? 2017年 gdtech. All rights reserved.
//
#import "ViewController.h"
#import "ZSZWebVIew.h"
@interface ViewController ()<ZSZWebViewDelegate>
@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