• OC与JS交互之WKWebView


    一、WKWebView介绍

    WKWebView是Apple在iOS8推出的Webkit框架中的负责网页的渲染与展示的类,相比UIWebView速度更快,占用内存更少,支持更多的HTML特性。

    (1)常用属性及方法

    // 导航代理
    @property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
    // UI代理
    @property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
    // 页面标题, 一般使用KVO动态获取
    @property (nullable, nonatomic, readonly, copy) NSString *title;
    // 页面加载进度, 一般使用KVO动态获取
    @property (nonatomic, readonly) double estimatedProgress;
    // 可返回的页面列表, 已打开过的网页, 有点类似于navigationController的viewControllers属性
    @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
    // 页面url
    @property (nullable, nonatomic, readonly, copy) NSURL *URL;
    // 页面是否在加载中
    @property (nonatomic, readonly, getter=isLoading) BOOL loading;
    // 是否可返回
    @property (nonatomic, readonly) BOOL canGoBack;
    // 是否可向前
    @property (nonatomic, readonly) BOOL canGoForward;
    // WKWebView继承自UIView, 所以如果想设置scrollView的一些属性, 需要对此属性进行配置
    @property (nonatomic, readonly, strong) UIScrollView *scrollView;
    // 是否允许手势左滑返回上一级, 类似导航控制的左滑返回
    @property (nonatomic) BOOL allowsBackForwardNavigationGestures;
    //自定义UserAgent, 会覆盖默认的值 ,iOS 9之后有效
    @property (nullable, nonatomic, copy) NSString *customUserAgent
    
    
    // 带配置信息的初始化方法
    // configuration 配置信息
    - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
    // 加载请求
    - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
    // 加载HTML
    - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
    // 返回上一级
    - (nullable WKNavigation *)goBack;
    // 前进下一级, 需要曾经打开过, 才能前进
    - (nullable WKNavigation *)goForward;
    // 刷新页面
    - (nullable WKNavigation *)reload;
    // 根据缓存有效期来刷新页面
    - (nullable WKNavigation *)reloadFromOrigin;
    // 停止加载页面
    - (void)stopLoading;
    // 执行JavaScript代码
    - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

    (2)WKWebViewConfiguration

    // 通过此属性来执行JavaScript代码来修改页面的行为
    @property (nonatomic, strong) WKUserContentController *userContentController;
    //***********下面属性一般不需要设置
    // 首选项设置,  
    //可设置最小字号, 是否允许执行js
    //是否通过js自动打开新的窗口
    @property (nonatomic, strong) WKPreferences *preferences;
    // 是否允许播放媒体文件
    @property (nonatomic) BOOL allowsAirPlayForMediaPlayback
    // 需要用户来操作才能播放的多媒体类型
    @property (nonatomic) WKAudiovisualMediaTypes mediaTypesRequiringUserActionForPlayback
    // 是使用h5的视频播放器在线播放, 还是使用原生播放器全屏播放
    @property (nonatomic) BOOL allowsInlineMediaPlayback;

    (3)WKUserContentController

    WKUserContentController 是JavaScript与原生进行交互的桥梁, 主要使用的方法有:

    // 注入JavaScript与原生交互协议
    // JS 端可通过 window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 发送消息
    - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
    // 移除注入的协议, 在deinit方法中调用
    - (void)removeScriptMessageHandlerForName:(NSString *)name;
    // 通过WKUserScript注入需要执行的JavaScript代码
    - (void)addUserScript:(WKUserScript *)userScript;
    // 移除所有注入的JavaScript代码
    - (void)removeAllUserScripts;

    二、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

    // 创建一个新的WebView
    - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;

    剩下三个代理方法全都是与界面弹出提示框相关的,针对于web界面的三种提示框(警告框、确认框、输入框)分别对应三种代理方法。

    // 界面弹出警告框
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(void (^)())completionHandler;
    // 界面弹出确认框
    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
    // 界面弹出输入框
    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;
     

    (3)WKScriptMessageHandler

    这个协议中包含一个必须实现的方法,这个方法是native与web端交互的关键,它可以直接将接收到的JS脚本转为OC或Swift对象。使用WKUserContentController注入的交互协议, 需要遵循WKScriptMessageHandler协议, 在其协议方法中获取JavaScript端传递的事件和参数:

    // 从web界面中接收到一个脚本时调用
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

    WKScriptMessage包含了传递的协议名称及参数, 主要从下面的属性中获取:

    // 协议名称, 即上面的add方法传递的name
    @property (nonatomic, readonly, copy) NSString *name;
    // 传递的参数
    @property (nonatomic, readonly, copy) id body;

    三、WKWebView-协议拦截

    (1)JS调OC

    JS代码:

            var loginBtn = document.getElementById("loginID");
            loginBtn.onclick = function () {
                var token = "js_tokenString";
                loginSuccess(token);
            }
            //登录成功
            function loginSuccess(token) {
                var action = "loginSuccess";
                jsToOc(action, token);
            }
    
            //JS调用OC入口
            function jsToOc(action, params) {
                var url = "jsToOc://" + action + "?" + params;
                window.location.href = url;
            }        

    OC代码:

    //WKWeView在每次加载请求前会调用此方法来确认是否进行请求跳转
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
        
        NSString *ss = navigationAction.request.URL.scheme;
        if ([navigationAction.request.URL.scheme caseInsensitiveCompare:@"jsToOc"] == NSOrderedSame) {
            decisionHandler(WKNavigationActionPolicyCancel);
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:navigationAction.request.URL.host message:navigationAction.request.URL.query delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
            
        }else{
             decisionHandler(WKNavigationActionPolicyAllow);
        }
    }
    
    //! WKWebView在每次加载请求完成后会调用此方法
    - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
        
        [webView evaluateJavaScript:@"document.title" completionHandler:^(NSString *title, NSError *error) {
            self.title = title;
        }];
    }

    (2)OC调JS

    JS代码:

            function ocToJs(action, params) {
                document.getElementById("returnValue").innerHTML = action + '?' + params;
            }    

    OC代码:

    #pragma mark - Action
    - (void)loginBtn01Action:(UIButton *)btn{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.wkWebView evaluateJavaScript:@"ocToJs('loginSucceed', 'oc_tokenString')" completionHandler:^(id response, NSError *error) {}];
        });
    }

    四、WKWebView-WKScriptMessageHandler协议

    WKScriptMessageHandler是WebKit提供的一种在WKWebView上进行JS消息控制的协议。

    (1)JS调OC

    实现原理:
    1、JS与iOS约定好jsToOc方法,用作JS在调用iOS时的方法;
    2、iOS使用WKUserContentController的-addScriptMessageHandler:name:方法监听name为jsToOc的消息;
    3、JS通过window.webkit.messageHandlers.jsToOc.postMessage()的方式对jsToOc方法发送消息;
    4、iOS在-userContentController:didReceiveScriptMessage:方法中读取name为jsToOc的消息数据message.body。

    [userContentController addScriptMessageHandler:self name:@"jsToOc"]会引起循环引用问题。一般来说,在合适的时机removeScriptMessageHandler可以解决此问题。比如:在-viewWillAppear:方法中执行add操作,在-viewWillDisappear:方法中执行remove操作。如下:

    [_webView.configuration.userContentController removeScriptMessageHandlerForName:@"jsToOc"];

    JS代码:

            var loginBtn = document.getElementById("loginID");
            loginBtn.onclick = function () {
                var token = "js_tokenString";
                loginSuccess(token);
            }
            //登录成功
            function loginSuccess(token) {
                var action = "loginSuccess";
                //jsToOc(action, token);
                window.webkit.messageHandlers.jsToOc.postMessage(action);
            }

    OC代码:

    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
        [userContentController addScriptMessageHandler:self name:@"jsToOc"];
        
        //! 使用添加了ScriptMessageHandler的userContentController配置configuration
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
        configuration.userContentController = userContentController;
        
        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        config.preferences.minimumFontSize = 18;
        config.userContentController = userContentController;
    #pragma mark - WKScriptMessageHandler
    //! WKWebView收到ScriptMessage时回调此方法
    - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
    {
        if ([message.name caseInsensitiveCompare:@"jsToOc"] == NSOrderedSame) {
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:message.name message:message.body delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
            NSLog(@"%@", message.body);
        }else if ([message.name caseInsensitiveCompare:@"showMobile"] == NSOrderedSame){
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:message.name message:message.body delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
            [alertView show];
        }
    }

    五、WKWebView-WKUIDelegate协议

    因为WKWebView的特性。JS在调用alert()、confirm()和prompt()方法时没有反应。若要正常使用这三个方法,iOS需要实现WKUIDelegate中的三个方法模拟JS的这三个方法。

    JS调OC

    JS代码:

            //! 调用alert(message)
            function showAlert() {
                alert("js_alertMessage");
            }
        
            //! 调用confirm(message)
            function showConfirm() {
                confirm("js_confirmMessage");
            }
        
            //! 调用prompt(prompt, defaultMessage)
            function showPrompt() {
                prompt("js_prompt", "js_prompt_defaultMessage");
            }

    OC代码:

    #pragma mark - WKUIDelegate
    //! alert(message)
    - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
        
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            completionHandler();
        }];
        [alertController addAction:cancelAction];
        [self presentViewController:alertController animated:YES completion:nil];
    }
    
    //! confirm(message)
    - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
        
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Confirm" message:message preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            completionHandler(NO);
        }];
        UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler(YES);
        }];
        [alertController addAction:cancelAction];
        [alertController addAction:confirmAction];
        [self presentViewController:alertController animated:YES completion:nil];
    }
    
    //! prompt(prompt, defaultText)
    - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
        
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:nil preferredStyle:UIAlertControllerStyleAlert];
        [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
            textField.placeholder = defaultText;
        }];
        UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            completionHandler(alertController.textFields[0].text);
        }];
        [alertController addAction:confirmAction];
        [self presentViewController:alertController animated:YES completion:nil];
    }

    参考:https://www.jianshu.com/p/e23aa25d7514 

         https://www.jianshu.com/p/905b40e609e2

       https://www.cnblogs.com/markstray/p/5757264.html

    GitHub:https://github.com/hongsheng1024/RunTime

  • 相关阅读:
    MATLAB2019a安装
    每日日报6
    HTML表格
    HTML常见表单元素
    HTML特殊符号
    CTF-WEB:攻防世界-bug(综合应用)
    CTF-WEB:文件上传和 webshell
    CTF-WEB:BurpSuite 工具应用
    CTF-WEB:后台扫描与备份泄露
    CTF-WEB:PHP 伪协议
  • 原文地址:https://www.cnblogs.com/whongs/p/9880289.html
Copyright © 2020-2023  润新知