iOS与js交互(一)----UIWebView

在开发中经常会遇到iOS与HTML混编的情况,iOS加载HTML可以使用UIWebView和WKWebView.本文先来介绍这两种方案中的UIWebView.

一,UIWebView简介

UIWebView的属性:

// 代理属性 重点需要知道代理方法的使用
@property (nullable, nonatomic, assign) id  delegate;

// 这个是webView内部的scrollView 只读,但是利用这个属性,设置scrollView的代理,就可以控制整个webView的滚动事件
@property(nonatomic, readonly, strong) UIScrollView *scrollView;

// webView的请求,这个属性一般在整个加载完成后才能拿到
@property (nullable, nonatomic, readonly, strong) NSURLRequest *request;

// A Boolean value indicating whether the receiver can move backward. (read-only)
// If YES, able to move backward; otherwise, NO.
// 如果这个属性为YES,才能后退
@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack;

// A Boolean value indicating whether the receiver can move forward. (read-only)
// If YES, able to move forward; otherwise, NO.
// 如果这个属性为YES,才能前进
@property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward;

// A Boolean value indicating whether the receiver is done loading content. (read-only)
// If YES, the receiver is still loading content; otherwise, NO.
// 这个属性很好用,如果为YES证明webView还在加载数据,所有数据加载完毕后,webView就会为No
@property (nonatomic, readonly, getter=isLoading) BOOL loading;

//A Boolean value determining whether the webpage scales to fit the view and the user can change the scale.
//If YES, the webpage is scaled to fit and the user can zoom in and zoom out. If NO, user zooming is disabled. The default value is NO.
// YES代表网页可以缩放,NO代表不可以缩放
@property (nonatomic) BOOL scalesPageToFit;

// 设置某些数据变为链接形式,这个枚举可以设置如电话号,地址,邮箱等转化为链接
@property (nonatomic) UIDataDetectorTypes dataDetectorTypes NS_AVAILABLE_IOS(3_0);

// iPhone Safari defaults to NO. iPad Safari defaults to YES
// 设置是否使用内联播放器播放视频
@property (nonatomic) BOOL allowsInlineMediaPlayback NS_AVAILABLE_IOS(4_0); 

// iPhone and iPad Safari both default to YES
// 设置视频是否自动播放
@property (nonatomic) BOOL mediaPlaybackRequiresUserAction NS_AVAILABLE_IOS(4_0); 

// iPhone and iPad Safari both default to YES
// 设置音频播放是否支持ari play功能
@property (nonatomic) BOOL mediaPlaybackAllowsAirPlay NS_AVAILABLE_IOS(5_0); 

// iPhone and iPad Safari both default to NO
// 设置是否将数据加载入内存后渲染界面
@property (nonatomic) BOOL suppressesIncrementalRendering NS_AVAILABLE_IOS(6_0); 

// default is YES
// 设置用户是否能打开keyboard交互
@property (nonatomic) BOOL keyboardDisplayRequiresUserAction NS_AVAILABLE_IOS(6_0); 

/* IOS7 */ 以后的新特性
// 这个属性用来设置一种模式,当网页的大小超出view时,将网页以翻页的效果展示,枚举如下:
@property (nonatomic) UIWebPaginationMode paginationMode NS_AVAILABLE_IOS(7_0);
typedef NS_ENUM(NSInteger, UIWebPaginationMode) { 
UIWebPaginationModeUnpaginated, //不使用翻页效果 
UIWebPaginationModeLeftToRight, //将网页超出部分分页,从左向右进行翻页 
UIWebPaginationModeTopToBottom, //将网页超出部分分页,从上向下进行翻页 
UIWebPaginationModeBottomToTop, //将网页超出部分分页,从下向上进行翻页 
UIWebPaginationModeRightToLeft //将网页超出部分分页,从右向左进行翻页 
};

// This property determines whether certain CSS properties regarding column- and page-breaking are honored or ignored. 
// 这个属性决定CSS的属性分页是可用还是忽略。默认是UIWebPaginationBreakingModePage 
@property (nonatomic) UIWebPaginationBreakingMode paginationBreakingMode NS_AVAILABLE_IOS(7_0);

// 设置每一页的长度
@property (nonatomic) CGFloat pageLength NS_AVAILABLE_IOS(7_0);

// 设置每一页的间距
@property (nonatomic) CGFloat gapBetweenPages NS_AVAILABLE_IOS(7_0);

// 获取页数
@property (nonatomic, readonly) NSUInteger pageCount NS_AVAILABLE_IOS(7_0);

UIWebView的代理方法:

    // Sent before a web view begins loading a frame.请求发送前都会调用该方法,返回NO则不处理这个请求
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

    // Sent after a web view starts loading a frame. 请求发送之后开始接收响应之前会调用这个方法
    - (void)webViewDidStartLoad:(UIWebView *)webView;

    // Sent after a web view finishes loading a frame. 请求发送之后,并且服务器已经返回响应之后调用该方法
    - (void)webViewDidFinishLoad:(UIWebView *)webView;

    // Sent if a web view failed to load a frame. 网页请求失败则会调用该方法
    - (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;

UIWebView的对象方法:

// 加载Data数据创建一个webView
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL

// 加载本地HTML创建一个webView
- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL

// 加载一个请求创建一个webView
- (void)loadRequest:(NSURLRequest *)request

// 刷新网页
- (void)reload;

// 停止网页加载内容
- (void)stopLoading;

// 后退
- (void)goBack;

// 前进
- (void)goForward;

// 执行JS方法
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script

二,UIWebView自带方法交互

以下所有代码demo:点击获取

OC调用js

[self.webView stringByEvaluatingJavaScriptFromString:@"cli()"];

// 获取当前页面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];
 
// 获取当前页面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];

js调用OC(OC代码)

/**使用UIWebView自带方法进行js调用OC的时候,在这个方法中讲oc方法注入js*/
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    /**获取点击页面加载的url,此url为前端与移动端约定好的,比如这个demo,约定好的请求头就是'demo',然后在'://后面添加不同的字段来判断不同的事件,可以看indexDemo.html中的js写法'*/
    NSString *requestString = request.URL.absoluteString;
    
    /**以'://将URL分割'*/
    NSArray *tmpArr = [requestString componentsSeparatedByString:@"://"];
    if ([tmpArr.firstObject isEqualToString:@"demo"]) {
        /**通过不同的字段来进行不同的事件处理*/
        if ([tmpArr.lastObject isEqualToString:@"goSleep"]) {
            [self AlertWithStr:@"去睡觉吧"];
        } else if ([tmpArr.lastObject isEqualToString:@"goEat"]) {
            [self AlertWithStr:@"去吃饭吧"];
        } else if ([tmpArr.lastObject isEqualToString:@"goDrink"]) {
            [self AlertWithStr:@"去喝水吧"];
        }
        return NO;
    }
    return YES;
}

js调用OC(js代码)

var btn=function  (id) {
            if (id == "sleep") {
                location.href = "demo://goSleep"
            } else if (id == "water") {
                location.href = "demo://goDrink"
            } else if (id == "food") {
                location.href = "demo://goEat"
            }
        }

三,使用javascriptCore进行交互

JavaScriptCore 简介

iOS7 中新加入的 JavaScriptCore.framework 可能被大多数开发人员所忽略,但是如果你之前就在项目中用过自己编译JavaScriptCore来处理 JavaScript,那么你需要重新关注一下 JavaScriptCore.framework。

JavaScriptCore 是苹果 Safari 浏览器的 JavaScript 引擎,或许你之前听过 Google 的 V8 引擎,在 WWDC 上苹果演示了最新的 Safari,据说 JavaScript 处理速度已经大大超越了 Google 的 Chrome,这就意味着 JavaScriptCore 在性能上也不输 V8 了。

其实 JavaScriptCore.framework 在 OS X 平台上很早就存在的,不过接口都是纯 C 语言的,而在 iOS 平台,苹果没有开放该 framework,所以不少需要在 iOS app 中处理 JavaScript 的都得自己从开源的 WebKit 中编译出 JavaScriptCore.a,接口也是纯 C 语言的。可能是苹果发现越来越多的程序使用了自编译的 JavaScriptCore,干脆做个顺水人情将 JavaScriptCore.framework 开放了,同时还提供了 Objective-C 的接口。

JavaScriptCore和我们相关的类只有五个:

JSContext
JS执行的环境,同时也通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。

JSValue
JS对象在JSVirtualMachine中的一个强引用,其实就是Hybird对象。我们对JS的操作都是通过它。并且每个JSValue都是强引用一个context。同时,OC和JS对象之间的转换也是通过它.

JSManagedValue
JS和OC对象的内存管理辅助对象。由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。

JSVirtualMachine
JS运行的虚拟机,有独立的堆空间和垃圾回收机制。

JSExport
一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。

代码示例

OC调用js(OC代码)

/**获取上下文,固定写法*/
JSContext *context=[self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    NSString *tmp = @"inputtext('哈哈哈哈哈哈')";
    [context evaluateScript:tmp];

OC调用js(js代码)

function inputtext (text) {
        document.querySelector('.test').innerHTML = text;
      }

1,block方式

block也可以称作闭包和匿名函数,使用block可以很方便的将OC中的单个方法暴露给JS调用,JSCore会自动将这个Block包装成一个JS方法.

js调用OC(OC代码)

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    __weak JSCoreViewController *weakSelf = self;
    context[@"go"] = ^(id str){
/**不要在block内部直接使用JSContext和JSValue,因为Block会强引用它里面用到的外部变量,如果直接在Block中使用JSValue的话,那么这个JSvalue就会被这个Block强引用,而每个JSValue都是强引用着它所属的那个JSContext的,而这个Block又是注入到这个Context中,所以这个Block会被context强引用,这样会造成循环引用,导致内存泄露。不能直接使用JSContext的原因同理。正确的驶入方法如下:*/
        NSArray *tmp = [JSContext currentArguments];
        for (JSValue *m in tmp) {
            NSString *mm = m.toString;
            if ([mm isEqualToString:@"water"]) {
                [weakSelf AlertWithStr:@"去喝水"];
            } else if ([mm isEqualToString:@"sleep"]) {
                [weakSelf AlertWithStr:@"去睡觉"];
            } else if ([mm isEqualToString:@"food"]) {
                [weakSelf AlertWithStr:@"去吃饭"];
            }
        }
    };
}

js调用OC(js代码)

var btn=function  (id) {
            go(id);
        }

2,JSExport方式

通过JSExport 协议可以很方便的将OC中的对象暴露给JS使用,且在JS中用起来就和JS对象一样。
自定义model.h

@protocol DemoModelProtocol 
/**这个方法得命名需要注意,
 OC的方法名字应该与js名字统一,
 OC:- (void)name:(NSString *)name Something:(NSString *)something;
 js:
 
 OC:- (void)test;
 js:
 */
- (void)name:(NSString *)name Something:(NSString *)something;
- (void)test;

@end

@interface DemoModel : NSObject 


/**VC*/
@property (nonatomic, weak)UIViewController *VC;
@end

自定义model.m

@implementation DemoModel
- (void)name:(NSString *)name Something:(NSString *)something
{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:name message:something preferredStyle:UIAlertControllerStyleAlert];
    
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        
    }];
    [alert addAction:action];
    [_VC presentViewController:alert animated:YES completion:nil];
}

- (void)test
{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"试试" preferredStyle:UIAlertControllerStyleAlert];
    
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        
    }];
    [alert addAction:action];
    [_VC presentViewController:alert animated:YES completion:nil];
}
@end

js调用OC(OC代码)

-(void)webViewDidFinishLoad:(UIWebView *)webView
{
    JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];  
    DemoModel *model = [[DemoModel alloc] init];
    model.VC = self;
    context[@"test"] = model;
}

js调用OC(js代码)


        

三,使用WebViewJavascriptBridge进行交互

初始化webView

UIWebView *webView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    [self.view addSubview:webView];
    [WebViewJavascriptBridge enableLogging];
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    [self.bridge setWebViewDelegate:self];
    NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewJavascriptBridgeDemo" ofType:@"html"];
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
    [self jsCalliOS];

这段代码是固定的,必须要放到js中

        function setupWebViewJavascriptBridge(callback) {
            if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
            if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
            window.WVJBCallbacks = [callback];
            var WVJBIframe = document.createElement('iframe');
            WVJBIframe.style.display = 'none';
            WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
            document.documentElement.appendChild(WVJBIframe);
            setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
        }

        setupWebViewJavascriptBridge(function(bridge) {
                                 /* 所有与iOS交互的JS代码放这里!*/
                                                                      
                                 })

OC调用js(OC代码)

[self.bridge callHandler:@"changeHH" data:@"你好,我是胡杨" responseCallback:^(id responseData) {
        NSLog(@"%@",responseData);
    }];

OC调用js(js代码)

 /**修改hhhh*/
bridge.registerHandler('changeHH', function(data, responseCallback) {
                                                            //处理oc给的传参
                                                            inputtext(data)
                                                            var responseData = { 'result':'handle success' }
                                                            //处理完,回调传值给oc
                                                            responseCallback(responseData)
                                                            })

js调用OC(OC代码)

- (void)jsCalliOS
{
    [self.bridge registerHandler:@"go" handler:^(id data, WVJBResponseCallback responseCallback) {
        if ([data[@"id"] isEqualToString:@"sleep"]) {
            [self AlertWithStr:@"去睡觉" responseCallback:responseCallback];
        } else if ([data[@"id"] isEqualToString:@"food"]) {
            [self AlertWithStr:@"去吃饭" responseCallback:responseCallback];
        } else if ([data[@"id"] isEqualToString:@"water"]) {
            [self AlertWithStr:@"去喝水" responseCallback:responseCallback];
        }
    }];
}

js调用OC(js代码)

btn = function(id) {
         bridge.callHandler('go', {'id':id}, function(response) {
                 //处理oc过来的回调
                 inputtext(response)
        })
 }

本文demo:点击获取

参考和相关文章:
https://blog.csdn.net/qq_21051503/article/details/78199440
https://www.cnblogs.com/yajunLi/p/6369257.html
深度交互(点击图片放大):
https://www.jianshu.com/p/e6a01c74f026
https://blog.csdn.net/longzs/article/details/50595616
https://www.jianshu.com/p/a329cd4a67ee
http://www.cocoachina.com/ios/20170720/19958.html

你可能感兴趣的:(iOS与js交互(一)----UIWebView)