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 <JavaScriptCore/JavaScriptCore.h>

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

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

@implementation LSLWebTestViewController

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

#pragma mark - <UIWebViewDelegate>
// 其中“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 - <JSObjcDelegate>

- (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 <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
  • JS在调用OC注册方法的时候要用下面的方式
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
  • 实现如下:
static NSString *const kGetUserInfoKey = @"getUserInfo";    // OC调用JS无参
static NSString *const kGotoPaymentKey = @"gotoPayment";    // OC调用JS有参

@interface LSLWKWebTestViewController () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler>
@property (nonatomic, strong) LSLWKScriptMessageHandler *messageHandle;
@end

@implementation LSLWKWebTestViewController

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

#pragma mark - <WKScriptMessageHandler>

- (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!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • 前言 关于UIWebView的介绍,相信看过上文的小伙伴们,已经大概清楚了吧,如果有问题,欢迎提问。 本文是本系列...
    Dark_Angel阅读 28,850评论 67 291
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,082评论 4 62
  • 前言 关于UIWebView的介绍,相信看过上文的小伙伴们,已经大概清楚了吧,如果有问题,欢迎提问。 本文是本系列...
    CoderLF阅读 8,960评论 2 12
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,988评论 25 707
  • 看了某位前辈写的日常小短文,手痒,我也来写。 1 毕业第三天、北漂了,2015年的北京,月租能便宜到350,我也算...
    SKY天阅读 74评论 0 0