原生与 H5 的交互

2016-07-20 21:29:57 +08:00
 tunnyios

欢迎关注个人博客:tunnycoder.com

在 iOS 开发中很多时候我们会和 WebView 打交道,并且很多应用都采用了 webView 的混合编程技术。 iOS 的 webview 有 2 个类,一个叫UIWebView,另一个是WKWebView。两者的基础方法都差不多。 WkWebView 是苹果在 iOS8 中引入的新组件,性能更好,如果不需要乡下兼容 iOS8 以前的版本,可以考虑用 WKWebView 。

iOS7 以前版本中的交互方式

Objective-C 调用 JavaScript

iOS7 以前,Objective-C调用JavaScript的方式只有一中,就是通过 UIWebView 对象的stringByEvaluatingJavaScriptFromString:方法。

/**
 * 执行一段 JavaScript 代码,并返回字符串类型的返回值
 */
UIWebView *webView = [[UIWebView alloc] init];
// 返回结果 @"2"
NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"1+1"];

// 调用 js 方法
NSString *result2 = [webView stringByEvaluatingJavaScriptFromString:
                        @"alert('hello js');"];

JavaScript 调用 Objective-C

iOS7 以前,JavaScript调用Objective-C的方法有两种:

URL 请求拦截

在 Objective-C 中实现UIWebViewDelegate代理,实现shouldStartLoadWithRequest方法,该方法可以监听到 UIWebView 中发出的 URL 请求,通过与 H5 协商一个 URL 通信协议,来拦截指定的 URL ,做相应的操作。

HTML

<div class="suitaqu wrap">
    <div class="stq-headerbar blue"><a href="stqnative://back" class="back"><i class="glyphicon glyphicon-menu-left"></i> </a>关于我们</div>

Objective-C

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
                                                 navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *url = request.URL.absoluteString;
    NSRange nativeRange = [url rangeOfString:@"stqnative://"];
    if (nativeRange.location != NSNotFound) { // url 的协议头是 stqnative://
        // do something
        // 返回 NO 以阻止 `URL` 的加载或者跳转
        // return NO;
    }

    return YES;
}

监听 Cookie

在 UIWebView 中, Objective-C同样可以通过NSHTTPCookieManagerCookiesChangedNotification事件以监听 cookie 的变化,来做相应的处理。当 JavaScript 修改 document.cookie 后, Objective-C 可以通过分析 cookie 以得到信息.

NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
[defaultCenter addObserverForName:NSHTTPCookieManagerCookiesChangedNotification
                           object:nil
                            queue:nil
                       usingBlock:^(NSNotification *notification) {
                           NSHTTPCookieStorage *cookieStorage = notification.object;
                           // do something with cookieStorage
                       }];

说明

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

iOS7 以后版本中的交互方式

iOS7 中引入了JavaScriptCore, 使得JavaScriptObjective-C更容易相互操作。

JavaScriptCore 几种类型解释

Objective-C 调用 JavaScript

-(void)webViewDidFinishLoad:(UIWebView *)webView  
{  
    //网页加载完成调用此方法  

    //获取当前 JS 环境
    JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];  
    NSString *alertJS=@"alert('hello js')"; //准备执行的 js 代码  
    [context evaluateScript:alertJS];//通过 oc 方法调用 js 的 alert  

}

JavaScript 调用 Objective-C

JavaScript 调用 Objective-C 有两种方法:直接调用 JS注入模型

直接调用 JS

HTML

<input type="button" value="测试 log" onclick="log('测试');" />

Objective-C

-(void)webViewDidFinishLoad:(UIWebView *)webView  
{  
    //网页加载完成调用此方法  

    //获取当前 JS 环境  
    JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];   

    //其中 log 是 js 的方法名称,赋给是一个 block 里面是 iOS 代码  
    //此方法最终将打印出所有接收到的参数
    context[@"log"] = ^() {  
        NSArray *args = [JSContext currentArguments];  
        for (id obj in args) {  
            NSLog(@"%@",obj);  
        }  
    };  
}  

注入模型的方式

此种情况是: HTML 中是通过一个对象来调用 JS 中的方法的,此处就需要用到JSExprot,只要添加了JSExport协议的协议,所规定的方法,变量等就会对 JS 开放,我们就可以通过 JS 调用到。

HTML

<a href="#" class="col-xs-6 viewcike" title="2">
  <div class="shadow">
    <div class="thumb" style="background-image:url( http://xxxxxx.jpeg)"></div>
    <div class="bottom">
      <h4 class="text-overflow stq-name">音乐喷泉~</h4>
      <div class="text-overflow dateandlocation">
        <div class="pull-left">大雁塔景区</div>
        <div class="pull-right zancount">1</div></div>
        <div class="text-overflow dateandlocation">
        <div class="pull-left">2016-4-8</div>
        <div class="pull-right plcount">0</div>
      </div>
    </div>
  </div>
</a>

JavaScript

i.callNative = function (t, n) {
        return console.log("namespace STQN is ", e.STQN), e.STQN || (e.STQN = {
            say: function (t) {
                console.log("call native action ", t);
                var e = JSON.parse(t);
                return "authError" === e.action ? i.go("login.html") : void 0
            }
        }), e.STQN.say(JSON.stringify({action: t, data: n}))
    }

$(document).on("click", ".viewcike", function (t) {
        t.preventDefault(), STQ.callNative("viewCikeImage", {cikes: i, current: 1 * $(this).attr("title")})
    })

:稍微解释一下此处,这里用的是jQuery Mobile框架,点击事件通过 viewcike 这个 class 来进行绑定,执行相应的方法。调用STQ.callNative方法,一步一步最终调用e.STQN.say(JSON.stringify({action: t, data: n})因此要在当前 JS 环境中,监听STQN这个对象并实现say ** PS:如果 js 是一个参数或者没有参数的话 就比较简单,我们的方法名和 js 的方法名保持一致即可;如果 js 是多个参数的话 我们代理方法的所有变量前的名字连起来要和 js 的方法名字一样 ** 方法。 Objective-C 中需要做的就是,创建一个模型并且规定一个 实现了JSExport协议 的协议,在模型中实现相应的方法。 Objective-C

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

//首先创建一个实现了 JSExport 协议的协议  
@protocol STQJSObjectProtocol <JSExport>
JSExportAs
(say  /** JSObjectAction 作为 js 方法的别名 */,
 - (void)JSObjectAction:(NSString *)message
 );
@end

@protocol STQJSObjectDelegate <NSObject>
@optional
/**
 *  监听 js 方法
 *  @param message acton 和 data
 */
- (void)JSObjectToOCWithMessage:(NSDictionary *)message;
@end

@interface STQJSObject : NSObject <STQJSObjectProtocol>
@property (nonatomic, weak) id<STQJSObjectDelegate> delegate;
@end


#import "STQJSObject.h"

@implementation STQJSObject
- (void)JSObjectAction:(NSString *)message
{
  //JSON 格式的字符串转成字典
    NSDictionary *dict = [STQUtiltiy dictionaryWithJsonString:message];
    if ([self.delegate respondsToSelector:@selector(JSObjectToOCWithMessage:)]) {
        [self.delegate JSObjectToOCWithMessage:dict];
    }
}

//控制器中执行
-(void)webViewDidFinishLoad:(UIWebView *)webView  
{  
  //获取当前 JS 环境
  _content = [_detailWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
  // 打印异常,由于 JS 的异常信息是不会在 OC 中被直接打印的,所以我们在这里添加打印异常信息,
  _content.exceptionHandler =
  ^(JSContext *context, JSValue *exceptionValue)
  {
      context.exception = exceptionValue;
      NSLog(@"%@", exceptionValue);
  };

  //获取 JS 事件
  STQJSObject *testJO=[STQJSObject new];
  testJO.delegate = self;
  _content[@"STQN"]=testJO;
}

#pragma mark - jsModel 代理事件
- (void)JSObjectToOCWithMessage:(NSDictionary *)message
{
  //在代理中执行相应的操作,应注意改方法是异步的,不是主线程执行,因此如果需要界面操作,需要 GCD 回主线程操作。
    NSLog(@"delegate msg is %@, current is %d", message, [NSThread currentThread].isMainThread);
}

:

JSExportAs
(callNative,
 -(void) callNative:(JSValue*)value
 );

//在.m 文件中,实现 callNative 方法
-(void) callNative:(JSValue*)value
{
NSString * text = [value valueForProperty:@"action"];//打印"viewCikeImage"
JSValue * func =  [value valueForProperty:@"callFun"]; //这里是 JS 参数中的 func;

//调用这个函数
[func callWithArguments:@[@"参数"]];
}

UIWebView 和 WKWebView

UIWebView 内存占用巨大, iOS8 中苹果给出了一个新的高性能 WebView 解决方案,使用Nitro JavaScript引擎,性能、稳定性、功能都得到大幅提升。WKWebView不支持JavaScriptCore的方式但提供message handler的方式为JavaScriptObjective-C通信.

关于 WKWebView 部分,下次再写~。~

未完~待续 ING...

6687 次点击
所在节点    iOS
10 条回复
param
2016-07-20 21:34:42 +08:00
我仿佛又听到有人在背后偷偷 @我
tuimaochang
2016-07-20 22:45:27 +08:00
贴代码的好评!
tuimaochang
2016-07-20 22:48:37 +08:00
已 rss
dangyuluo
2016-07-20 23:09:24 +08:00
不错。
BenX
2016-07-20 23:36:53 +08:00
eddiechen
2016-07-21 10:06:59 +08:00
赞!
lwbjing
2016-07-21 10:31:16 +08:00
姿势贴,感谢分享...
qq2511296
2016-07-21 10:44:09 +08:00
666 不错的文章
fish19901010
2016-07-21 17:13:42 +08:00
我们现在就是用这个方式自己做 Hybird 。
jinzhe
2016-07-23 14:39:17 +08:00
WKWebView 有个问题就是用 f7 框架的时候无法 ajax 加载 html

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/293797

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX