UIWebView和WKWebView与JavaScript交互简介

一、简介

苹果在iOS2推出了UIWebView,用于移动端加载网页等资源。但是它从设计之初就比较笨重,存在占用内存过多,内存泄漏等问题。所以在iOS8.0之后,苹果对UIWebView进行优化和重造,推出了WKWebView用于取代UIWebView。

二、大纲

  • WKWebView对比UIWebView的性能优势;
  • WKWebView加载本地html网页的优雅方式;
  • UIWebView的封装使用;
  • WKWebView的封装使用;
  • UIWebView与JS交互;
  • WKWebView与JS交互;

三、详解

1、相对于UIWebView,WKWebView有以下的改进:

  • 在性能、稳定性、功能方面有很大提升,直观体现是内存占用更少;
  • 允许JavaScript的Nitro库加载并使用(UIWebView中限制);
  • 支持了更多的HTML5特性;
  • 高达60fps的滚动刷新率以及内置手势;
  • 将UIWebViewDelegate与UIWebView重构成了14类与3个协议(详见SDK);
  • 开放了一些重要的属性,比如加载进度条(estimatedProgress)等。

2、WKWebView加载本地html网页的优雅方式

- (void)loadLocalHtmlWithName:(NSString *)name {
    if (!name || name.length <= 0) {
        NSLog(@"本地不存在网页名为:%@的网页。", name);
        return;
    }
    self.localHtmlName = [name copy];
    NSURL *baseURL     = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
    NSString *htmlPath = [[NSBundle mainBundle] pathForResource:name ofType:@"html"];
    NSString *htmlCont = [NSString stringWithContentsOfFile:htmlPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:nil];
    [self.webView loadHTMLString:htmlCont baseURL:baseURL];
}

3、UIWebView封装使用看源码

4、WKWebView的封装使用看源码

(1) 加载的状态回调 (WKNavigationDelegate)

  • 用来追踪加载过程(页面开始加载、加载完成、加载失败)的方法:
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;
  • 页面跳转的代理方法:
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

(2)新增的WKUIDelegate协议

  • 主要用于WKWebView处理web界面的三种提示框(警告框、确认框、输入框)如果不实现这些代理方法,H5中的alert将不会被调起。
// 警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    // TODO: do something
}
// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
   // TODO: do something
}

5、UIWebView与JS交互

UIWebView 使用的 JavaScriptCore 框架,交互时为 JavaScript 运行的上下文环境 JSContext 注入对象 Bridge;WKWebView 使用的 WebKit 框架,交互时为 webkit.messageHandlers 注入对象。

#import 

@protocol JSObjcDelegate 
// JS调用OC无参
- (void)getUserInfo;
// JS调用OC有参
- (void)gotoPayment:(NSString *)orderID;
@end

@interface LSLWebTestViewController () 
@property (nonatomic, strong) JSContext *jsContext;
@end

@implementation LSLWebTestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadLocalHtmlWithName:@"index"];
}

#pragma mark - 
// 其中“shopping”为与JS交互的对象
- (void)lsl_webViewDidFinishLoad:(UIWebView *)webView {
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext[@"shopping"] = self;
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
    };
}

#pragma mark - 

- (void)getUserInfo {
    NSLog(@"JS调用OC成功!");
    // TODO: do something
    [self callUserInfoBack];
}

- (void)callUserInfoBack {
    NSLog(@"OC调用JS方法,并传递参数!");
    
    // OC调用JS有参
    JSValue *Callback = self.jsContext[@"callback"];
    [Callback callWithArguments:@[@"OC调用JS方法成功!"]];
}

- (void)gotoPayment:(NSString *)orderID {
    if (!orderID || orderID.length <= 0) {
        NSLog(@"商城订单,未获取到订单编号。");
        return;
    }
    NSLog(@"成功获取到JS传递过来的订单编号orderID = %@ ", orderID);
    
    // OC调用JS无参
    JSValue *Callback = self.jsContext[@"alerCallback"];
    [Callback callWithArguments:nil];
}

6、WKWebview与JS交互

WKWebview不需要借助JavaScriptCore或者webJavaScriptBridge,它是通过WKUserContentController实现js native的交互。简单的说就是先注册约定好的方法,然后再调用。

  • 注意点:
    • 使用WKScriptMessageHandler时,最好创建一个代理对象,然后通过代理对象回调指定的self,防止内存泄露;
    • 如果使用了addScriptMessageHandler,要在的dealloc中释放一下否则会造成内存泄漏。

WKWebView里面注册供JS调用的方法

  • 通过WKUserContentController提供的以下方法,实现注册:
/** name:表示JS调用的OC方法名称; scriptMessageHandler:是接受事件处理的对象。 */
- (void)addScriptMessageHandler:(id )scriptMessageHandler name:(NSString *)name;
  • JS在调用OC注册方法的时候要用下面的方式
window.webkit.messageHandlers..postMessage()
  • 实现如下:
static NSString *const kGetUserInfoKey = @"getUserInfo";    // OC调用JS无参
static NSString *const kGotoPaymentKey = @"gotoPayment";    // OC调用JS有参

@interface LSLWKWebTestViewController () 
@property (nonatomic, strong) LSLWKScriptMessageHandler *messageHandle;
@end

@implementation LSLWKWebTestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // webView的配置对象对传出数据的名字进行监听,此时负责接收JS消息处理的对象不要忘记履行协议
    [self.webView.configuration.userContentController addScriptMessageHandler:self.messageHandle name:kGetUserInfoKey];
    [self.webView.configuration.userContentController addScriptMessageHandler:self.messageHandle name:kGotoPaymentKey];
    
    [self loadLocalHtmlWithName:@"index2"];
}

#pragma mark - 

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if([message.name isEqualToString:kGetUserInfoKey]) {
        [self getUserInfo];
    } else if ([message.name isEqualToString:kGotoPaymentKey]) {
        [self gotoPayment:message.body];
    }
}

#pragma mark - JS 与 OC 交互

- (void)getUserInfo {
    NSLog(@"JS调用OC成功!");
    // TODO: do something
    [self callUserInfoBack];
}

- (void)callUserInfoBack {
    NSLog(@"OC调用JS方法,并传递参数!");
    
    // OC调用JS有参
    NSString *js = @"callback('OC调用JS方法成功!')";
    [self.webView evaluateJavaScript:js completionHandler:^(id _Nullable objct, NSError * _Nullable error) {
    }];
}

- (void)gotoPayment:(NSString *)orderID {
    if (!orderID || orderID.length <= 0) {
        NSLog(@"商城订单,未获取到订单编号。");
        return;
    }
    NSLog(@"成功获取到JS传递过来的订单编号orderID = %@ ", orderID);
    
    // OC调用JS无参
    NSString *js = @"alerCallback()";
    [self.webView evaluateJavaScript:js completionHandler:^(id _Nullable objct, NSError * _Nullable error) {
    }];
}

具体的实现请查阅github项目,欢迎start!

你可能感兴趣的:(UIWebView和WKWebView与JavaScript交互简介)