OC与JS交互

JS与OC交互初探

webView http wkwebView webKit 苹果内核
#pragma mark - private

- (void)getSum{

    NSLog(@"123344444");
    NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:@"showAlert('HELLO')"];
    NSLog(@"result == %@",result);
}

#pragma mark - 响应

- (IBAction)didClickLetfAction:(id)sender {
  

}
- (IBAction)didClickRightItemClick:(id)sender {
    NSLog(@"响应按钮");
    NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:@"showAlert('HELLO')()"];
    NSLog(@"result == %@",result);
    }

#pragma mark - UIWebViewDelegate

/**
 这些都是JS响应的样式
 UIWebViewNavigationTypeLinkClicked,        点击
 UIWebViewNavigationTypeFormSubmitted,      提交
 UIWebViewNavigationTypeBackForward,        返回
 UIWebViewNavigationTypeReload,             刷新
 UIWebViewNavigationTypeFormResubmitted,    重复提交
 UIWebViewNavigationTypeOther               其他

 */
// 加载所有请求数据,以及控制是否加载
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
  
    NSLog(@"%@",request.URL.scheme); // 标识 我们自己协议
    NSLog(@"%@",request.URL.host);   // 方法名
    NSLog(@"%@",request.URL.pathComponents);  // 参数

    // JS 调用OC 的原理就是 拦截URL
    NSString *scheme = request.URL.scheme;
    if ([scheme isEqualToString:@"tzedu"]) {
        NSLog(@"来了,我们自定义的协议");
        
        NSArray *args = request.URL.pathComponents;
        NSString *methodName = args[1];
        
        // 方法1
  //        if ([methodName isEqualToString:@"getSum"]) {
  //            [self getSum];
  //        }
        // 方法2
        SEL methodSel = NSSelectorFromString(methodName);
        if ([self respondsToSelector:methodSel]) {
  #pragma clang diagnostic push
            // 让编译器忽略错误
  #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            // 让编译器出栈,恢复状态,继续编译后续的代码!
            [self performSelector:methodSel withObject:args[2]];
  #pragma clang diagnostic pop
        }
        // 方法3
  //        objc_msgSend(self,methodSel,args[2],args[3]);
        
        ((void (*) (id, SEL, id))(objc_msgSend))(self, methodSel,args[2]);
        
        // 定义kc_msgSend 函数指针  (void *) 替代了 (void (*)(id, SEL, id))
        void (*kc_msgSend) (id, SEL, id) = (void *)objc_msgSend;
        kc_msgSend(self,methodSel,args[2]);
        
        return NO;
    }
    
    return YES;
  }

// 开始加载
- (void)webViewDidStartLoad:(UIWebView *)webView{
    NSLog(@"****************华丽的分界线****************");
    NSLog(@"开始加载咯!!!!");
    }

// 加载完成
- (void)webViewDidFinishLoad:(UIWebView *)webView{
    //死的!!!
    NSString *titlte = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    self.title = titlte;
    
    NSLog(@"****************华丽的分界线****************");
    NSLog(@"加载完成了咯!!!!");
    }

// 加载失败
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
    NSLog(@"****************华丽的分界线****************");
    NSLog(@"加载失败了咯,为什么:%@",error);
    }

JSCore

//上下文
#import "JSContext.h"
//把js数据类型转成oc
#import "JSValue.h"
//提供底层资源
#import "JSManagedValue.h"
//Machine信息
#import "JSVirtualMachine.h"
//协议,js调用OC,oc只要实现这个协议就好了
#import "JSExport.h"
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol KCProtocol 

- (void)letShowImage;
JSExportAs(getSum, -(int)getSum:(int)num1 num2:(int)num2);

@end

@interface KC_JSObject : NSObject
@end
#import "KC_JSObject.h"

@implementation KC_JSObject

- (void)letShowImage{
  
    NSLog(@"打开相册,上传图片");
  }

- (int)getSum:(int)num1 num2:(int)num2{
    return num1+num2;
    }
@end
#import "JSCoreMoreVC.h"
#import "KC_JSObject.h"

@interface JSCoreMoreVC ()<UIWebViewDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (nonatomic, strong) UIWebView *webView;
@property (nonatomic, strong) UILabel *showLabel;
@property (nonatomic, strong) JSContext *jsContext;
@property (nonatomic,strong) UIImagePickerController *imagePicker;

@end

@implementation JSCoreMoreVC

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.showLabel];

    self.webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 124, self.view.bounds.size.width, self.view.bounds.size.height-124)];
    self.webView.delegate        = self;
    [self.view addSubview:self.webView];
    
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"index2.html" withExtension:nil];;
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [self.webView loadRequest:request];
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"点我" style:UIBarButtonItemStyleDone target:self action:@selector(didClickRightAction)];
    
    

}

- (void)didClickRightAction{
  
    NSLog(@"rightBarButtonItem");
  }

#pragma mark - UIWebViewDelegate
// 加载完成
- (void)webViewDidFinishLoad:(UIWebView *)webView{
  
    NSString *titlt = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    self.title = titlt;
    
    //JSContext就为其提供着运行环境 H5上下文
    JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext = jsContext;
    __weak typeof(self) weakSelf = self;

    // 异常处理
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        context.exception = exception;
        NSLog(@"exception == %@",exception);
        NSLog(@"%@",context);
    };
    
    // 提供全局变量
    [self.jsContext evaluateScript:@"var arr = [3, 'Cooci', 'abc'];"];
    
    self.jsContext[@"showMessage"] = ^() {
        
        JSValue *thisValue = [JSContext currentThis];
        NSLog(@"thisValue = %@",thisValue);
        JSValue *cValue = [JSContext currentCallee];
        NSLog(@"cValue = %@",cValue);
        NSArray *args = [JSContext currentArguments];
        NSLog(@"来了:%@",args);
        
        NSDictionary *dict = @{@"name":@"cooci",@"age":@18};
        
        [[JSContext currentContext][@"ocCalljs"] callWithArguments:@[dict]];
    };
    
    // 因为是全局变量 可以直接获取
    JSValue *arrValue = self.jsContext[@"arr"];
    NSLog(@"arrValue == %@",arrValue);
    
    //纠正用法
    //    JSValue *value = [JSValue valueWithObject:@"test“ inContext:context];
    //    JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value andOwner:self];

    
    self.jsContext[@"showDict"] = ^(JSValue *value) {
        
        NSArray *args = [JSContext currentArguments];
        JSValue *dictValue = args[0];
        NSDictionary *dict = dictValue.toDictionary;
        NSLog(@"%@",dict);
        
        // 模拟用
        int num = [[arrValue.toArray objectAtIndex:0] intValue];
        num += 10;
        NSLog(@"arrValue == %@   num == %d",arrValue.toArray,num);
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.showLabel.text = dict[@"name"];
        });
    };
    
    
    //异常收集
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        weakSelf.jsContext.exception = exception;
        NSLog(@"exception == %@",exception);
    };
    
    
    // JS 操作对象
    KC_JSObject *kcObject = [[KC_JSObject alloc] init];
    self.jsContext[@"kcObject"] = kcObject;
    

    // 打开相册
    self.jsContext[@"getImage"] = ^() {
        
        weakSelf.imagePicker = [[UIImagePickerController alloc] init];
        weakSelf.imagePicker.delegate = weakSelf;
        weakSelf.imagePicker.allowsEditing = YES;
        weakSelf.imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        [weakSelf presentViewController:weakSelf.imagePicker animated:YES completion:nil];
    };
    

}

- (void)dealloc{
    NSLog(@"dealloc :走了");
    }

#pragma mark -- UIImagePickerDelegate

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
    NSLog(@"info---%@",info);
    UIImage *resultImage = [info objectForKey:@"UIImagePickerControllerEditedImage"];
    NSData *imgData = UIImageJPEGRepresentation(resultImage, 0.01);
    NSString *encodedImageStr = [imgData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    [self removeSpaceAndNewline:encodedImageStr];
    [self dismissViewControllerAnimated:YES completion:nil];
    NSString *imageString = [self removeSpaceAndNewline:encodedImageStr];
    NSString *jsFunctStr = [NSString stringWithFormat:@"showImage('%@')",imageString];
    [self.jsContext evaluateScript:jsFunctStr];
    }

- (NSString *)removeSpaceAndNewline:(NSString *)str
{
    NSString *temp = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    temp = [temp stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    temp = [temp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    return temp;
}


- (void)openAlbum{
    self.imagePicker = [[UIImagePickerController alloc] init];
    self.imagePicker.delegate = self;
    self.imagePicker.allowsEditing = YES;
    self.imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    [self presentViewController:self.imagePicker animated:YES completion:nil];
    }

#pragma mark - lazy

- (UILabel *)showLabel{
    if (!_showLabel) {
        _showLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 64+20, 100, 30)];
        _showLabel.textColor = [UIColor orangeColor];
        _showLabel.font = [UIFont systemFontOfSize:16];
        _showLabel.text = @"我是一个文本";
    }
    return _showLabel;
    }

@end

WKWView交互

- (void)viewDidLoad {
    [super viewDidLoad];

    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    //屏幕比例配置
    NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    [wkUController addUserScript:wkUScript];
    configuration.userContentController = wkUController;
    
    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
    self.webView.navigationDelegate = self;
    self.webView.UIDelegate = self;
    [self.view addSubview:self.webView];
    
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    [self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];

}
- (IBAction)didClickLeftAction:(id)sender {
  
    [self.navigationController pushViewController:[WKMessageHandleVC new] animated:YES];
  }
- (IBAction)didClickRightItemAction:(id)sender {
    NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"登陆成功"];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];

}



#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    // 拦截 -- 超链接  --  自定义
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"tzedu"]) {
        
        NSString *host = [URL host];
        if ([host isEqualToString:@"jsCallOC"]) {
            NSMutableDictionary *temDict = [self decoderUrl:URL];
            NSString *username = [temDict objectForKey:@"username"];
            NSString *password = [temDict objectForKey:@"password"];
            NSLog(@"%@---%@",username,password);
            
        }else{
            NSLog(@"不明地址 %@",host);
        }
        //取消掉。实现拦截
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    self.title = webView.title;
    }


#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
  
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提醒" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
  
    [self presentViewController:alert animated:YES completion:nil];
}


#pragma mark - 解析URL地址
- (NSMutableDictionary *)decoderUrl:(NSURL *)URL{
    NSArray *params =[URL.query componentsSeparatedByString:@"&"];
    NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
    for (NSString *paramStr in params) {
        NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
        if (dicArray.count > 1) {
            NSString *decodeValue = [dicArray[1] stringByRemovingPercentEncoding];
            [tempDic setObject:decodeValue forKey:dicArray[0]];
        }
    }
    return tempDic;
    }


#pragma mark - 大小适应
- (WKUserContentController *)wkwebViewScalPreferences{
  
    NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    [wkUController addUserScript:wkUScript];
    
    
    //    WKPreferences *preferences = [[WKPreferences alloc] init];
    //    preferences.javaScriptCanOpenWindowsAutomatically = YES;
    //    preferences.minimumFontSize = 100.0;
    //    configuration.preferences = preferences;
    
    return wkUController;
  }

#pragma mark - WKNavigationDelegate
//请求之前,决定是否要跳转:用户点击网页上的链接,需要打开新页面时,将先调用这个方法。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
//接收到相应数据后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
//页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 主机地址被重定向时调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载完毕时调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
//跳转失败时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 如果需要证书验证,与使用AFN进行HTTPS证书验证是一样的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler;
//9.0才能使用,web内容处理中断时会触发
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);

@end
  
  - (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];

   //循环引用self->web->config->self
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"messgaeOC"];
}

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    //解决循环引用
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"messgaeOC"];
    }

#pragma mark - didClickRightAction

- (void)didClickRightAction{
  
    NSLog(@"didClickRightAction");
    
    NSString *jsStr2 = @"showAlert('messageHandle:OC-->JS')";
    [self.webView evaluateJavaScript:jsStr2 completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];

}

#pragma mark - WKScriptMessageHandler。执行脚本必须实现

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
  
    NSLog(@"message == %@ --- %@",message.name,message.body);
    // 展示不出来 -- 会打开 : iframe为0
    //    if (message.name) {
    //        <#statements#>
    //    }
  }


#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
  
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提醒" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
  
    [self presentViewController:alert animated:YES completion:nil];
}

WebScriptBridge

- (void)viewDidLoad {
    [super viewDidLoad];

    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    [wkUController addUserScript:wkUScript];
    configuration.userContentController = wkUController;
    
    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
    self.webView.UIDelegate = self;
    [self.view addSubview:self.webView];
    
    NSString *urlStr = [[NSBundle mainBundle] pathForResource:@"index.html" ofType:nil];
    NSURL *fileURL = [NSURL fileURLWithPath:urlStr];
    [self.webView loadFileURL:fileURL allowingReadAccessToURL:fileURL];
    //绑定一个视图。UI/WK都可以。
    self.wjb = [WebViewJavascriptBridge bridgeForWebView:self.webView];
    // 如果你要在VC中实现 UIWebView的代理方法 就实现下面的代码(否则省略)
     [self.wjb setWebViewDelegate:self];
    
    //js调用OC
    [self.wjb registerHandler:@"jsCallsOC" handler:^(id data, WVJBResponseCallback responseCallback) {
        //回来就是主线程
        NSLog(@"currentThread == %@",[NSThread currentThread]);
        //直接回调data
        //responseCallback可以继续跟踪
        NSLog(@"data == %@ -- %@",data,responseCallback);
    }]; 
}

- (IBAction)didClickLeftAction:(id)sender {
  
    //    // 如果不需要参数,不需要回调,使用这个
    [self.wjb callHandler:@"OCCallJSFunction"];
    //    // 如果需要参数,不需要回调,使用这个
    [self.wjb callHandler:@"OCCallJSFunction" data:@"一个字符串"];
    // 如果既需要参数,又需要回调,使用这个
    [self.wjb callHandler:@"OCCallJSFunction" data:@"oc调用JS咯" responseCallback:^(id responseData) {
        
        NSLog(@"currentThread == %@",[NSThread currentThread]);
        
        NSLog(@"调用完JS后的回调:%@",responseData);
    }];
  }