iOS&Web 双向交互及WKWebView基础使用

本文主要介绍iOS&Web交互逻辑及示例代码,及给WKWebView增加加载进度条。

一、交互方式

1、使用原生(开发语言:Objective-C)自带的WKWebView类,iOS7之后出了JavaScriptCore.framework用于与JS交互,实现加载H5网页界面。WKWebView控件,可以完全只借助 iOS 自带的框架进行 OC & JS 交互。

二、WKWebView 原生交互原理

1、通过 WKUserContentController把需要观察的 JS 执行函数注册起来。
2、然后通过一个协议方法,将所有注册过的 JS 函数执行的参数传递到此协议方法中。
3、最后在通过WKWebView以下的方法,返回结果给JS。

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

三、OC端代码示例

(1)继承WKWebView协议代理方法,初始化WKWebView。
① 初始化

@interface TwoPageVC ()
@property (nonatomic, strong) WKWebView *wWebView;

@property(nonatomic,strong)NSString *webRequestMethodName;

@property(nonatomic,strong)NSString *webResponseMethodName;

@property (strong, nonatomic) UIImagePickerController *picker;

@property(nonatomic,strong)UIImageView *imageV;

@end
@implementation TwoPageVC
- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"webView页面";
    self.picker.delegate = self;
    self.picker.allowsEditing = YES;
    self.view.backgroundColor = KWhiteColor;
    WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc]init];
    self.wWebView = [[WKWebView alloc]initWithFrame:CGRectZero configuration:config];
    self.wWebView.navigationDelegate = self;
    self.localHtml = @"OnePageTest";
    NSString *path = [NSString stringWithFormat:@"JSOC.bundle/%@",self.localHtml];
    NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:path withExtension:@".html"];
    [self.wWebView loadRequest:[NSURLRequest requestWithURL:fileUrl]];
    [self.view addSubview:self.wWebView];
    [self.view addSubview:self.lab];
    
    [self.wWebView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.top.equalTo(self.view);
        make.bottom.equalTo(self.lab.mas_top).offset(-SIZEWIDTH_X(16, ScreenWidth));
    }];
    [self.lab mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.view);
  make.bottom.equalTo(self.view.mas_bottom).offset(-SIZEWIDTH_X(50, ScreenWidth));
    }];

    self.imageV.hidden = YES;
    [self.view addSubview:self.imageV];
    
    [self.imageV mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view.mas_centerX);
        make.bottom.equalTo(self.view.mas_bottom).offset(-SIZEWIDTH_X(50, ScreenWidth));
        make.size.mas_equalTo(CGSizeMake(SIZEWIDTH_X(50, ScreenWidth), SIZEWIDTH_X(50, ScreenWidth)));
    }];
    self.userCC = config.userContentController;
    //此处相当于监听了JS方法
    [self.userCC addScriptMessageHandler:self name:@"test1Click"];
    [self.userCC addScriptMessageHandler:self name:@"test2Click"];
    [self.userCC addScriptMessageHandler:self name:@"test3Click"];
    [self.userCC addScriptMessageHandler:self name:@"test4Click"];
}
-(UILabel *)lab
{
    if (!_lab) {
        _lab = [[UILabel alloc]init];
        _lab.font = [UIFont systemFontOfSize:SIZEWIDTH_X(18, ScreenWidth)];
        _lab.textColor = [UIColor redColor];
        _lab.textAlignment = NSTextAlignmentCenter;
        _lab.text = @"OC用于显示js传给OC的值的label";
    }
    return _lab;
}
@end

(2)实现WKWebView协议代理方法及自定义方法
① WKWebView开始加载方法

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
    NSLog(@"开始加载");
}

② WKWebView加载完成方法

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    NSLog(@"加载完成");
}

③ WKWebView加载失败方法

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"加载失败");
}

④接收JS传值的方法

#pragma mark  WKScriptMessageHandler delegate
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
/*
@abstract The name of the message handler to which the message is sent.
message.name  JS调用OC的方法名

 @abstract The body of the message.
 @discussion Allowed types are NSNumber, NSString, NSDate, NSArray,
 NSDictionary, and NSNull.
message.body   JS给OC传的数据
*/
    self.webRequestMethodName = message.name;
    self.webResponseMethodName =message.body[@"webResponseMethod"];
    _lab.text = message.body[@"data"][@"user_name"];
    [self clickMethodsWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName];
}

⑤OC根据Web不同的触发方法实现不同的逻辑

#pragma mark ==========不同触发方法实现不同的逻辑
-(void)clickMethodsWithWebRequestMethod:(NSString *)webRequestMethod andWebResponseMethod:(NSString *)webResponseMethod{
    self.imageV.hidden = YES;
    if ([self.webRequestMethodName isEqualToString:@"test1Click"]) {
        //    获取用户名
        [self callbackMethondWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName andJsonDic:[self dicWithWebRequestMethodName:self.webRequestMethodName andWebResponseMethodName:self.webResponseMethodName andMsg:@"操作成功" andCode:1 andDataDic:@{}]];
    }else if ([self.webRequestMethodName isEqualToString:@"test2Click"]){
        //    拍照 唤起相机
        [self evokeCamerWithCallBackName:webRequestMethod];
        
    }else if ([self.webRequestMethodName isEqualToString:@"test3Click"]){
        //      支付宝支付
        [self callbackMethondWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName andJsonDic:[self dicWithWebRequestMethodName:self.webRequestMethodName andWebResponseMethodName:self.webResponseMethodName andMsg:@"OC接收到Web触发支付宝支付的请求" andCode:2 andDataDic:@{}]];
    }else if ([self.webRequestMethodName isEqualToString:@"test4Click"]){
        //        微信支付
        [self callbackMethondWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName andJsonDic:[self dicWithWebRequestMethodName:self.webRequestMethodName andWebResponseMethodName:self.webResponseMethodName andMsg:@"OC接收到Web触发微信支付的请求" andCode:2 andDataDic:@{}]];
    }
}

⑥OC回传给js的方法

#pragma mark =======OC回传js
-(void)callbackMethondWithWebRequestMethod:(NSString *)webRequestMethod andWebResponseMethod:(NSString *)webResponseMethod andJsonDic:( NSDictionary*)jsonDic {
    
    NSString *jsonStr = [self gs_jsonStringCompactFormatForDictionary:jsonDic];
    [_wWebView evaluateJavaScript:[NSString stringWithFormat:@"%@('%@')",self.webResponseMethodName,jsonStr] completionHandler:^(id _Nullable response, NSError * _Nullable error) {
        NSLog(@"webresponse = %@",response);
        NSLog(@"webError = %@",error);
    }];
}

⑦构造返回JS结果字典

#pragma mark -------构造字典

-(NSDictionary *)dicWithWebRequestMethodName:(NSString *)webRequestMethodName andWebResponseMethodName:(NSString *)webResponseMethodName andMsg:(NSString *)msg andCode:(NSInteger)code  andDataDic:(NSDictionary *)dataDic{
    if (dataDic.count == 0) {
        dataDic = @{};
    }
    NSDictionary *dic = [NSDictionary dictionary];
    dic = @{@"webRequestMethodName":webRequestMethodName,@"webResponseMethodName":webResponseMethodName,@"msg":msg,@"code":@(code),@"data":dataDic};
    return dic;
}

⑧返回JS结果字典转换成JSON字符串

#pragma mark -----字典转json字符串
-(NSString *)gs_jsonStringCompactFormatForDictionary:(NSDictionary *)dicJson {
    if (![dicJson isKindOfClass:[NSDictionary class]] || ![NSJSONSerialization isValidJSONObject:dicJson]) {
        return nil;
    }
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dicJson options:0 error:nil];
    
    NSString *strJson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    
    NSLog(@"响应方法回调strJson == %@",strJson);
    
    return strJson;
}

⑨VC销毁时,移除ScriptMessageHandler

- (void)dealloc {
   
    //最后, VC销毁的时候一定要把handler移除
    [self.userCC removeScriptMessageHandlerForName:@"test1Click"];
    [self.userCC removeScriptMessageHandlerForName:@"test2Click"];
    [self.userCC removeScriptMessageHandlerForName:@"test3Click"];
    [self.userCC removeScriptMessageHandlerForName:@"test4Click"];
}

⑩以h5按钮触发拍照为例

#pragma mark -------拍照
//初始化
- (UIImagePickerController *)picker
{
    if (!_picker) {
        _picker = [[UIImagePickerController alloc]init];
        _picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    }
    return _picker;
}

-(UIImageView *)imageV{
    if (!_imageV) {
        _imageV = [[UIImageView alloc]init];
    }
    return _imageV;
}

//获取拍照后图片
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    //    获取图片
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    self.imageV.hidden = NO;
    self.imageV.image = image;
    
    // 压缩一下图片再传
    NSData *imgData = UIImageJPEGRepresentation(image, 0.001);
    
    NSString *encodedImageStr = [imgData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    NSString *imageString = [self removeSpaceAndNewline:encodedImageStr];
    //    获取图片后返回
    [picker dismissViewControllerAnimated:YES completion:^{
        [self callbackMethondWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName andJsonDic:[self dicWithWebRequestMethodName:self.webRequestMethodName andWebResponseMethodName:self.webResponseMethodName andMsg:@"用户使用了图片" andCode:1 andDataDic:@{@"imageString":imageString}]];
    }];
}

// 图片转成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
{
    //    返回
    [picker dismissViewControllerAnimated:YES completion:^{
        [self callbackMethondWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName andJsonDic:[self dicWithWebRequestMethodName:self.webRequestMethodName andWebResponseMethodName:self.webResponseMethodName andMsg:@"用户取消使用相机" andCode:4 andDataDic:@{}]];
    }];
    
}

//判断相机是否可用

-(void)evokeCamerWithCallBackName:(NSString *)callBackName{
    BOOL isPicker = YES;
    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    //            判断相机是否可用
    if (authStatus ==AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied ) {
        isPicker = NO;
    }
    if (isPicker) {
        [self presentViewController:self.picker animated:YES completion:^{
            [self callbackMethondWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName andJsonDic:[self dicWithWebRequestMethodName:self.webRequestMethodName andWebResponseMethodName:self.webResponseMethodName andMsg:@"相机调起成功" andCode:2 andDataDic:@{}]];
        }];
        
    }else {
        //    oc调用js
        [self callbackMethondWithWebRequestMethod:self.webRequestMethodName andWebResponseMethod:self.webResponseMethodName andJsonDic:[self dicWithWebRequestMethodName:self.webRequestMethodName andWebResponseMethodName:self.webResponseMethodName andMsg:@"当前相机不可用,请检查是否开启了相机隐私权限" andCode:3 andDataDic:@{}]];
    }
}

(2)以URL形式加载网页时,增加进度条
①初始化

@interface TwoPageVC ()

@property(nonatomic,strong)UIProgressView *progressView;//加载进度条
@end

@implementation TwoPageVC
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.wWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]]];
    [self.wWebView addSubview:self.progressView];
    [self.progressView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.right.equalTo(self.wWebView);
        make.height.mas_equalTo(SIZEWIDTH_X(2, ScreenWidth));
    }];
}

-(UIProgressView *)progressView{
    if (!_progressView) {
        _progressView = [[UIProgressView alloc]initWithProgressViewStyle:UIProgressViewStyleDefault];
        //        未填充的进度条颜色
        _progressView.trackTintColor = [UIColor clearColor];
        // 设置进度条的色彩/进度条填充颜色
        _progressView.progressTintColor = [UIColor blueColor];
        // 设置初始的进度,防止用户进来就懵逼了(微信大概也是一开始设置的10%的默认值)
        [_progressView setProgress:0.1 animated:YES];
    }
    return _progressView;
}
@end

②KVO,监听WKWebView加载网页进度条方法

- (void)viewDidLoad {
    [self.wWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
    [self.wWebView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
}

③完成KVO监听进度条方法

// 完成监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    if ([object isEqual:self.wWebView] && [keyPath isEqualToString:@"estimatedProgress"]) {
        // 进度条
        CGFloat newprogress = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
        NSLog(@"打印进度值:%f", newprogress);
        if (newprogress == 1) { // 加载完成
            // 首先加载到头
            [self.progressView setProgress:newprogress animated:YES];
            // 之后0.3秒延迟隐藏
            __weak typeof(self) weakSelf = self;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
                weakSelf.progressView.hidden = YES;
                [weakSelf.progressView setProgress:0 animated:NO];
            });
        }else{
            // 加载中
            self.progressView.hidden = NO;
            [self.progressView setProgress:newprogress animated:YES];
        }
    }else if([object isEqual:self.wWebView] && [keyPath isEqualToString:@"title"]) {
        // 标题
        self.navigationItem.title = self.wWebView.title;
    }else{
        // 其他
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

④ VC销毁时,移除监听进度条方法

- (void)dealloc {
    [_wWebView removeObserver:self forKeyPath:@"estimatedProgress"];
    [_wWebView removeObserver:self forKeyPath:@"title"];
    
}

三、Web端代码示例

(1)建立一个OnePageTest.html文件,创建4个按钮



    
        
            欢迎来到web与OC的JS交互testDemon
            
            
    
        
        
        







h5用于显示OC传过来的参数的label

(2)建立一个OnePageTest.js文件,实现4个按钮的触发方法,并触发传值给OC

// 实现按钮的点击事件
function test1Click(){
    document.getElementById("test1");
    console.log('h5的test1打出来拉');
    user_info = {"webRequestMethod":"test1Click","webResponseMethod":"result2","data":{"user_name":"JSOCTestDemo","age":"18"}};
    /*jS给OC传值*/
    window.webkit.messageHandlers.test1Click.postMessage(user_info);
}
function test2Click(){
    document.getElementById("test2");
    console.log('h5的test2打出来拉');
    user_info = {"webRequestMethod":"test2Click","webResponseMethod":"result2","data":{"user_name":"拍照","age":"18"}};
    /*jS给OC传值*/
    window.webkit.messageHandlers.test2Click.postMessage(user_info);
}
function test3Click(){
    document.getElementById("test3");
    console.log('h5的test3打出来拉');
    user_info = {"webRequestMethod":"test3Click","webResponseMethod":"result2","data":{"user_name":"支付宝支付","age":"18"}};
    /*jS给OC传值*/
    window.webkit.messageHandlers.test3Click.postMessage(user_info);
}
function test4Click(){
    document.getElementById("test4");
    console.log('h5的test4打出来拉');
    user_info = {"webRequestMethod":"test4Click","webResponseMethod":"result2","data":{"user_name":"微信支付","age":"18"}};
    /*jS给OC传值*/
    window.webkit.messageHandlers.test4Click.postMessage(user_info);
}

(3)创建接收OC返回结果的方法。
//此方法可以动态设置,由Web端调起OC时,传入;也可以双方协议固定方法名。

//OC调用JS带参方法
function result2(a){
    console.log('OC调用了JS无参数的方法');
    document.getElementById("labelId").innerHTML = "oc调用了js方法 - 参数:"+a;
}

你可能感兴趣的:(iOS&Web 双向交互及WKWebView基础使用)