美文网首页Swift编程
iOS与js交互(一)----UIWebView

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

作者: 0胡杨0 | 来源:发表于2018-05-11 15:30 被阅读82次

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

    一,UIWebView简介

    UIWebView的属性:

    // 代理属性 重点需要知道代理方法的使用
    @property (nullable, nonatomic, assign) id <UIWebViewDelegate> 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 <JSExport>
    /**这个方法得命名需要注意,
     OC的方法名字应该与js名字统一,
     OC:- (void)name:(NSString *)name Something:(NSString *)something;
     js:<input type="button" id="food" onclick="test.nameSomething('B','去吃饭')" class="btn" value="去吃饭" />
     
     OC:- (void)test;
     js:<input type="button" id="sleep" onclick="test.test()" class="btn" value="去睡觉" />
     */
    - (void)name:(NSString *)name Something:(NSString *)something;
    - (void)test;
    
    @end
    
    @interface DemoModel : NSObject <DemoModelProtocol>
    
    
    /**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代码)

    <body>
            <div>
                <input type="button" id="water" onclick="test.nameSomething('C','去喝水')"  class="btn" value="去喝水" />
            </div>
            <div>
                <input type="button" id="food" onclick="test.nameSomething('B','去吃饭')" class="btn" value="去吃饭" />
            </div>
            <div>
                <input type="button" id="sleep" onclick="test.test()" class="btn" value="去睡觉" />
            </div>
        </body>
    

    三,使用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

        本文链接:https://www.haomeiwen.com/subject/babqrftx.html