Bootstrap

iOS基础之与HTML交互

目录
    1. WKWebView
    2. UIWebView+原生框架(javascriptcore.framework)
    3. 第三方WebViewJavascriptBridge框架
1. WKWebView(iOS8后推出)

简介

相比WebView更省内存。
iPhone 与 mac 中的 Safari 都是基于WKWebView 实现的。

使用

#import <WebKit/WebKit.h>    

    // <WKScriptMessageHandler,WKUIDelegate,WKNavigationDelegate>
    
    // 创建WKWebView
    WKWebView *webView=[[WKWebView alloc]initWithFrame:CGRectZero configuration:({
        // 1.创建配置类
        WKWebViewConfiguration *config=[WKWebViewConfiguration new];
        // 1.1设置偏好设置
        config.preferences=({
            WKPreferences *pre=[WKPreferences new];
            [pre setMinimumFontSize:10];                            // 最小字体(默认:0)
            [pre setJavaScriptEnabled:true];                        // js是否可用(默认:true)
            [pre setJavaScriptCanOpenWindowsAutomatically:false];   // js是否能通过打开窗口 (默认:false)
            pre;
        });
        // 1.2设置内容处理池
        config.processPool=[WKProcessPool new];
        // 1.3设置交互(通过js)
        [config setUserContentController:({
            WKUserContentController *userC=[WKUserContentController new];
            // 添加交互,当收到js调用ssbb后 回调didReceiveScriptMessage <WKScriptMessageHandler>
            [userC addScriptMessageHandler:self name:@"ssbb"];

            userC;
        })];

        // ---------- 其他常用属性 start----------
        // 设置 持久化
        // 一个WKWebsiteDataStore对象代表了被网页使用的各种类型的数据。包括cookies,磁盘文件,内存缓存以及持久化数据如WebSQL,IndexedDB数据库,local storage。
        [config setWebsiteDataStore:[WKWebsiteDataStore defaultDataStore]];
        // 设置 在web全部加载到内存前是否阻止其显示
        [config setSuppressesIncrementalRendering:true];
        // 设置 UserAgent中的应用名称
        [config setApplicationNameForUserAgent:@""];
        // 设置 是否允许AirPlay播放媒体(默认:true)
        [config setAllowsInlineMediaPlayback:true];
        // 设置 是否忽略缩放属性(默认:false)覆盖网页中的user-scalable HTML属性
        [config setIgnoresViewportScaleLimits:true];
        // 设置 需要检测的数据类型(可对相应的类型做处理,如链接则可跳转)
        [config setDataDetectorTypes:WKDataDetectorTypeLink];
        /*
         WKDataDetectorTypeNone             不检测(默认)
         WKDataDetectorTypePhoneNumber      电话
         WKDataDetectorTypeLink             链接
         WKDataDetectorTypeAddress          地址
         WKDataDetectorTypeCalendarEvent    日历提醒事件
         WKDataDetectorTypeTrackingNumber   跟踪号码/查询号/运单号
         WKDataDetectorTypeFlightNumber     航班号
         WKDataDetectorTypeLookupSuggestion 
         WKDataDetectorTypeAll              检测所有类型
         */
        // 设置 是否允许画中画播放?
        [config setAllowsPictureInPictureMediaPlayback:true];
        // 设置 选择内容的粒度级别
        [config setSelectionGranularity:WKSelectionGranularityDynamic];
        /*
         WKSelectionGranularityDynamic,     用户可自定义选择区域(默认)
         WKSelectionGranularityCharacter,   不可自定义选择区域
         */
        // 设置 是否使用在线的控制器(默认:false,使用本地的全屏控制器)
        [config setAllowsInlineMediaPlayback:true];
        // 设置 哪些视频需要用户手势才能自动播放
        [config setMediaTypesRequiringUserActionForPlayback:WKAudiovisualMediaTypeAll];
        /*
         WKAudiovisualMediaTypeNone     所有视频自动播放
         WKAudiovisualMediaTypeAudio    音频
         WKAudiovisualMediaTypeVideo    视频
         WKAudiovisualMediaTypeAll      所有都需要手势才能播放
         */
        //
        // ---------- 其他常用属性 end----------
        
        config;
    })];
    // 1.1 dele <WKUIDelegate,WKNavigationDelegate>
    [webView setUIDelegate:self];
    [webView setNavigationDelegate:self];
    // 1.2 loadRequest
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"url"]]];
    [self.view addSubview:webView];

常用

获取常用信息

    // 标题(readOnly)
    NSString *title=webView.title;
    // url地址(readOnly)
    NSURL *URL=webView.URL;  
    // 是否正在加载(readOnly)
    BOOL loading=webView.isLoading 
    // 加载进度(readOnly)
    double estimatedProgress=webView.estimatedProgress;  
    // 是否加密(readOnly)
    BOOL hasOnlySecureContent=webView.hasOnlySecureContent; 
    // scrollView(readOnly)
    UIScrollView *scrollView=webView.scrollView;
    // 调用js中的方法
    [webView evaluateJavaScript:@"" completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
    }];

    // 设置 是否支持手势前进后退(默认:false)
    [webView setAllowsBackForwardNavigationGestures:true];
    // 设置 是否允许预览链接(默认:true)
    [webView setAllowsLinkPreview:true];
加载请求

    // loadRequest
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"url"]]];
    // 加载fileURL
    [webView loadFileURL:[NSURL URLWithString:@""] allowingReadAccessToURL:[NSURL URLWithString:@""]];
    // 加载data
    [webView loadData:[NSData data] MIMEType:@"" characterEncodingName:@"" baseURL:[NSURL URLWithString:@""]];
    // 加载本地html
    NSString *urlStr=[[NSBundle mainBundle]pathForResource:@"index.html" ofType:nil];
    [webView loadHTMLString:[NSString stringWithContentsOfFile:urlStr encoding:NSUTF8StringEncoding error:nil] baseURL:[NSURL URLWithString:urlStr]];
向前向后

    // 是否允许向后(readOnly)
    BOOL canGoBack=webView.canGoBack;
    // 是否允许向前(readOnly)
    BOOL canGoForward=webView.canGoForward;
    // 向后
    [webView goBack];
    // 向前
    [webView goForward];
    // 重新加载
    [webView reload];
    // 重新加载初始网址
    [webView reloadFromOrigin];
    // 停止加载
    [webView stopLoading];
前进后退列表

    // 获取页面前进后退列表(readOnly)
    WKBackForwardList *list=webView.backForwardList;
    // 当前(readOnly)
    WKBackForwardListItem *item=list.currentItem;
    // 前一个(readOnly)
    WKBackForwardListItem *itemForward=list.forwardItem;
    // 后一个(readOnly)
    WKBackForwardListItem *itemBack=list.backItem;
    // 后列表(readOnly)
    NSArray *backList=list.backList;
    // 前列表(readOnly)
    NSArray *forwardList=list.forwardList;
    // 根据下标获取
    WKBackForwardListItem *item=[list itemAtIndex:0];

    // 前进或后退到指定页
    [webView goToBackForwardListItem:item];

    // (readOnly)
    NSURL *URL=item.URL;
    NSURL *initialURL=item.initialURL;
    NSString *title=item.title;

dele

#pragma mark dele <WKScriptMessageHandler>        @required

// js调用OC方法后调用
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    // message.name
    // message.body    
}
#pragma mark dele<WKUIDelegate>    @optional

// 创建webView后回调
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{
}
// 关闭webView后调用
-(void)webViewDidClose:(WKWebView *)webView{
}
// js中调用alert后调用
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
}
// js中调用confirm后调用
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
}
// js中调用prompt后调用
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
}


// 是否允许预览指定的WKPreviewElementInfo
- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo{
}
// 当用户发出了预览操作(比如3D Touch按压)时调用
- (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id <WKPreviewActionItem>> *)previewActions{
}
// 预览时用户触发pop操作(继续按压)时调用
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController{
}
#pragma mark dele<WKNavigationDelegate>      @optional

// 设置 是否允许跳转(发送请求之前)
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    
    // navigationAction.targetFrame navigationAction.sourceFrame
    
    NSString *hostStr=navigationAction.request.URL.host.lowercaseString;
    if(navigationAction.navigationType==WKNavigationTypeLinkActivated && ![hostStr containsString:@".com"]){
        // 不允许
        decisionHandler(WKNavigationActionPolicyCancel);
    }else{
        // 允许
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}
// 设置 是否允许跳转(发送请求并收到响应后)
-(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    
    // onlyRead:
    // navigationResponse.forMainFrame(是否是main frame) navigationResponse.response(获取响应response) navigationResponse.canShowMIMEType
}
// 重定向后调用(接收到服务器的跳转请求)
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
}

// 跳转失败后调用
-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{}
// https则调用
-(void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{}

// 页面开始加载时调用
-(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 withError:(NSError *)error{
}

// 内容处理中断时调用
-(void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
}
2. UIWebView+原生框架(javascriptcore.framework)
    OC中可以直接调用JS方法
    JS可通过拦截url间接调用OC
    #import <JavaScriptCore/JavaScriptCore.h>
    <UIWebViewDelegate>


        // 创建UIWebView
        UIWebView *webV=[UIWebView new];
        [self.view addSubview:webV];
        // 位置布局。。。
        // loadRequest(加载网页)
        [webV loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"url"]]];
其他常用属性
        // 
        [webV setScalesPageToFit:true];
        // 滚动速度:正常速,默认慢速
        [webV.scrollView setDecelerationRate:UIScrollViewDecelerationRateNormal];

dele

        // dele
        [webV setDelegate:self];

// 加载完成后调用
-(void)webViewDidFinishLoad:(UIWebView *)webView{

》》》》》OC调js
    // 初始化一些操作  (如:提交表单,插入内容,删除内容,修改内容,查询内容)
    [webView stringByEvaluatingJavaScriptFromString:@"js代码"];
    
举例:
    // 提交表单
    [webView stringByEvaluatingJavaScriptFromString:@"document.froms[0].submit();"];
    
    // 插入内容
    [webView stringByEvaluatingJavaScriptFromString:@" js 代码"];
    // 例:
    @"var script=document.createElement('script');"   // 可以是普通控件如img(.src .width .height)
    @"script.type='text/javascript';"
    @"script.text=\"function myFun(){"
    @"var f=document.getElementsByName(‘q’)[0];"
    @"f.value='11';"
    @"document.forms[0].submit();"
    @"}\";"
    @"document.getElementsByTagName('head')[0].appendChild(script);"
    
    // 删除内容
    [webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('a').remove()"];
    
    // 修改内容值、显示值
    [webView stringByEvaluatingJavaScriptFromString:@"document.getElementsByName('a')[0].value='123'"];
    [webView stringByEvaluatingJavaScriptFromString:@"document.getElementsByName('a')[0].innerHTML='h123'"];
    
    // 查询内容
    // url
    NSString *urlStr=[webView stringByEvaluatingJavaScriptFromString:@"document.location.href"];
    // title : document.title


    // 禁用 页面元素选择
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitUserSelect='none';"];    
    // 禁用 长按弹出ActionSheet
    [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitTouchCallout='none';"];





》》》》》 js调用OC    (方式一,原生JavaScriptCore.framework框架(iOS7))
    #import<JavaScriptCore/JavaScriptCore.h>  

    /* 2种方式:
        1、Block:暴露单个方法(不能直接使用JSValue、JSContext,造成循环引用)
        2、JSExport协议:暴露单个对象
     */

方式一(Block):
OC代码
     #import <JavaScriptCore/JavaScriptCore.h>
    // 获取 js代码的执行环境
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    // Block (注册)
    context[@"js中的函数名"] = ^{
        NSArray *arg = [JSContext currentArguments];
    };
    context[@"js中的函数名"] = ^(NSDictionary *dic){
        NSLog(@"函数的实参值:%@", dic[@"color"]);
    };
js代码
    function testClick(){
        var str1=document.getElementById("text1").value;
        var str2=document.getElementById("text2").value;
        函数名(str1,str2);
    }
      
方式二(<JSExport>):
    实现协议遵守<JSExport>,JS中调用时(会将方法转为驼峰式,也可以使用宏JSExportAs(sbAdd,+(void)method:(int)a with:(int)b))此宏只对带参有效
    JS中调用:对象.属性 , 对象.方法   (不能在这增加成员变量)
}



// 是否允许加载网络请求
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{

》》》》》 js调用OC    (方式二,拦截url)
    //
    NSString *urlStr=request.URL.absoluteString;
    //
    NSRange range=[urlStr rangeOfString:@"ssbb://"];
    if(range.location!=NSNotFound){
    
        NSString *method=[urlStr substringFromIndex:range.location+range.length];
        [self performSelector:NSSelectorFromString(method)];
        return false;
    }
    
    return true;
}
// 开始加载后调用
-(void)webViewDidStartLoad:(UIWebView *)webView{}
// 加载失败后调用
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{}

注意事项

1.JS值的包装
        JS中的值拿到OC中是不能直接用的
        不能作为OC对象的属性

    JSValue *value=[context evalutateScript:@"2+2"];     [value toInt32];
    /*
     OC type            JS type
     
     nil                undefined
     NSNull             null
     NSString           string
     NSNumber           number,boolean
     NSDictionary       Object object
     NSArray            Array object
     NSDate             Date Object
     NSBlock            Function Object
     id                 Wrapper object
     Class              Constructor object
     */
3. WebViewJavascriptBridge框架(第三方框架)
    原理:拦截URL

         pod 'WebViewJavascriptBridge'
         #import "WebViewJavascriptBridge.h"
        // 基于WKWebView,则不用再设置WKWebView 的navigationDelegate(navDele为bridge)
        // 基于UIWebView,则不用再设置dele(dele为bridge)
        
        // 0.开启日志调试
        [WebViewJavascriptBridge enableLogging];
        // 1.创建WKWebView或UIWebView
        // 2.创建JavascriptBridge
        WebViewJavascriptBridge *_webViewBridge=[WebViewJavascriptBridge bridgeForWebView:webView];
        [_webViewBridge setWebViewDelegate:self];
        // 2.1配置
        // js调OC(注册多个handleName,用于js中调用)
        [_webViewBridge registerHandler:@"getUserIdFromOC" handler:^(id data, WVJBResponseCallback responseCallback) {
            // data
            // callBack
            if(responseCallback){
                responseCallback(@{@"userId":@"ssbb"});
            }
        }];
        // OC调js
        [_webViewBridge callHandler:@"getUserName" data:@{@"name":@"ssbb"} responseCallback:^(id responseData) {
            // responseData
        }];
~~~~~~~~JS

<script>    
      /*这段代码固定,必须要放到js中(第一次加载HTML时起作用,目的是加载一次wvjbscheme://__BRIDGE_LOADED__,注册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)
      }
    
      /*与OC交互的所有JS方法都要放在此处注册,才能调用通过JS调用OC或者让OC调用这里的JS*/
      setupWebViewJavascriptBridge(function(bridge) {
       
       /*注册OC调JS*/
      bridge.registerHandler('openWebviewBridgeArticle', function() {
         log("openWebviewBridgeArticle was called with by ObjC")
      })
      /*注册OC调JS*/
      bridge.registerHandler('token', function(data, responseCallback) {
         log("G星爷: ", data)
         responseCallback({这里给我返回拼接后的地址})
      })

       /* 注册js调OC */
       document.getElementById('register').onclick = function () {
          bridge.callHandler('ww', {'blogdURL': 'weidsfdl'}, function(response) {
                          log('JS got response', response)
                          })
       }
     })
</script>
;