iOS开发技巧之:WKWebView 与 JS 交互和基本使用

导入库
#import <WebKit/WebKit.h>
1、WKWebView 创建 不注册 js 调用 OC 的方法
//初始化
 _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) ];
// UI代理
_webView.UIDelegate = self;
// 导航代理
_webView.navigationDelegate = self;
// 是否允许手势左滑返回上一级, 类似导航控制的左滑返回
_webView.allowsBackForwardNavigationGestures = YES;
//可返回的页面列表, 存储已打开过的网页 
//  WKBackForwardList * backForwardList = [_webView backForwardList];
Webview 加载本地HTML
 NSString *path = [[NSBundle mainBundle] pathForResource:@"JStoOC.html" ofType:nil];
NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
//加载本地html文件
[_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
Webview 加载网络HTML
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.chinadaily.com.cn"]];
[request addValue:[self readCurrentCookieWithDomain:@"http://www.chinadaily.com.cn"] forHTTPHeaderField:@"Cookie"];
[_webView loadRequest:request];
Webview 页面后退、前进和刷新
// 是否可以后退,返回 BOOL  YES 可以后退, NO 第一页,不可以后退,可以做 pop webView  操作
if ([_webView canGoBack]) {
//页面后退
    [_webView goBack];
}
else {
// pop webView 退出 webView 页面
}
// 是否可以前进,返回 BOOL  YES 可以前进, NO 第一页,不可以前进
if ([_webView canGoForward]) {
    //页面前进
    [_webView goForward];
}
else {
// 没有下一页
}
//刷新当前页面
[_webView reload];
2、WKWebView 创建注册 js 调用 OC 的方法,

\color{red} {jsCallOCMethodNames} 是一个 js 调用 OC 的方法名称的数组, 这些方法是不带返回值的,即void 方法,可以带参数
例如:JSCallOCMethod1(‘jsonString’){ }

jsCallOCMethodNames = @[@"JSCallOCMethod1",@"JSCallOCMethod2"];
防止循环引用 WebViewScriptMessageDelegate 所以创建一个弱引用的 WeakWebViewScriptMessageDelegate
// WKWebView 内存不释放的问题解决
@interface WeakWebViewScriptMessageDelegate : NSObject<WKScriptMessageHandler>
    
//WKScriptMessageHandler 这个协议类专门用来处理JavaScript调用原生OC的方法
@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
    
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
    
@end

@implementation WeakWebViewScriptMessageDelegate
    
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
    self = [super init];
    if (self) {
        _scriptDelegate = scriptDelegate;
    }
    return self;
}
    
#pragma mark - WKScriptMessageHandler
    //遵循WKScriptMessageHandler协议,必须实现如下方法,然后把方法向外传递
    //通过接收JS传出消息的name进行捕捉的回调方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    
    if ([self.scriptDelegate respondsToSelector:@selector(userContentController:didReceiveScriptMessage:)]) {
        [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
    }
}
    
@end
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKUserContentController *wkUController = [[WKUserContentController alloc] init];
//以下代码适配文本大小,由UIWebView换为WKWebView后,会发现字体小了很多,这应该是WKWebView与html的兼容问题,解决办法是修改原网页,要么我们手动注入JS
NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
//用于进行JavaScript注入
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];

[wkUController addUserScript:wkUScript];
// 创建一个弱引用的 WeakWebViewScriptMessageDelegate 防止循环引用
WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] init];

for (NSString *methodName in jsCallOCMethodNames) {
     // 添加方法名监听 (主要是这步)
     [wkUController addScriptMessageHandler:weakScriptMessageDelegate name:methodName];
}
config.userContentController = wkUController;
//初始化
 _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) configuration:config];
// UI代理
_webView.UIDelegate = self;
// 导航代理
_webView.navigationDelegate = self;
// 是否允许手势左滑返回上一级, 类似导航控制的左滑返回
_webView.allowsBackForwardNavigationGestures = YES;
//可返回的页面列表, 存储已打开过的网页 
//  WKBackForwardList * backForwardList = [_webView backForwardList];

核心代码是 [config.userContentController addScriptMessageHandler:self name:methodName] 这样就添加了一个 JS 调用 OC 的方法监听,当 JS 调用方法时,通过WKScriptMessageHandler 代理方法来获取 JS 调用 OC 的方法的处理

JS 主动调用 OC 的方法的处理
OC 代码

处理添加监听的方法名,即 JS 主动调用 OC 的方法

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController
      didReceiveScriptMessage:(WKScriptMessage *)message {
    // message.name  添加监听的方法名
    if ([message.name isEqualToString: @"JSCallOCMethod1"]) {
        //  message.body   是 `js` 传递的参数 ,一般是 json 字符串
        NSLog(@"MessageBody: %@", message.body);
    }
}
JS 代码

核心代码
window.webkit.messageHandlers.<添加监听的方法名>.postMessage(参数);
如何不需要参数 postMessage(' ') 这样处理

window.webkit.messageHandlers.JSCallOCMethod1.postMessage({"key1":"value1","key2":"value2"});
OC 主动调用 JS 的方法的处理

// OC 调用 JS 方法 并传参数 jsonString, 当不需要参数时,可以 ('') 这样处理

[self.webView evaluateJavaScript: @"OCCallJSMethod('jsonString')"
           completionHandler:^(id response, NSError * error) {
        NSLog(@"response: %@, \nerror: %@", response, error);
 }];
当 JS 需要调用 OC 带有参数和返回值的方法时
JS 代码

1、代用参数需要返回值的方法

let jsonString = window.prompt("jsCallOCReturnJsonStringMethod","参数jsonString");
//  jsonString  就是 OC 返回的 jsonString 

2、没有参数需要返回值的方法

let jsonString = window.prompt("jsCallOCReturnJsonStringMethod");
//  jsonString  就是 OC 返回的 jsonString 
OC 代码

这种方法不需要注册监听,是使用 UIDelegate 方法

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
    if (prompt) {
        // defaultText  是JS 传的JsonString参数
        if (defaultText.length > 0) {
              // 说明有参数
        }
        if ([prompt isEqualToString:@"jsCallOCReturnJsonStringMethod"]) {
            completionHandler(@"返回的结果,可以自定义");
        }
}

webview 设置 userAgent

   WKWebView *webView = [WKWebView new];
       [webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable oldAgent, NSError * _Nullable error) {
           if (![oldAgent isKindOfClass:[NSString class]]) {
               // 为了避免没有获取到oldAgent,所以设置一个默认的userAgent
               // Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
               oldAgent = [NSString stringWithFormat:@"Mozilla/5.0 (%@; CPU iPhone OS %@ like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E216", [[UIDevice currentDevice] model], [[[UIDevice currentDevice] systemVersion] stringByReplacingOccurrencesOfString:@"." withString:@"_"]];
           }
           
           NSString *userAgentSuffix = @"自己想要拼接的标识";
           //自定义user-agent
           if (![oldAgent hasSuffix:userAgentSuffix]) {
               NSString *newAgent =  [oldAgent stringByAppendingFormat:@" %@",userAgentSuffix];;
               [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":newAgent}];
               [[NSUserDefaults standardUserDefaults] synchronize];
               // 一定要设置customUserAgent,否则执行navigator.userAgent拿不到oldAgent
               webView.customUserAgent = newAgent;
           }
       }];
HTML 测试文件源码
<!DOCTYPE html>
<html>
<header>
    
<title id = "title">Title of this page</title>

<!--样式从File.css文件中获取-->
<link rel="stylesheet" type="text/css" href="File.css">
<!--样式改变适应手机屏幕大小-->
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<!--响应的方法从File.js文件中获取-->
<!--<script type="text/javascript" src="File.js"></script>-->

</header>

<body>
    // 防止 汉字在 APP 端乱码
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <!--    标签,css中设置样式时,会根据id来设置-->
    <p id = "wql">This is my first try to write Html5 file.</p>
    <br/>
    <!--    加粗的文本,css中设置样式时,会根据id来设置-->
    <b id = "myp">This text is bold</b>

    <!--    换行符-->
    <br/><br/><br/><br/>
    
    <!--    该按钮的目的是:点击后触发OC的方法-->
    <button type="button" onclick = "JSCallOCMethod1()">JSCallOCMethod1</button>
    <br/><br/>
    
    <button type="button" onclick = "JSCallOCMethod2()">JSCallOCMethod2</button>
    <br/><br/>
    
    <button type="button" onclick = "jsCallOCReturnMethod()">jsCallOCReturnMethod </button>

    <!--        定义各个方法-->
    <script type="text/javascript">
        
    // js 主动调用 oc  方法  ,function JSCallOCMethod1 这个名字无关,这个是JS 绑定的
    function JSCallOCMethod1(){
        //  JSCallOCMethod1 是 oc 注册监听的方法
window.webkit.messageHandlers.JSCallOCMethod1.postMessage({"key1":"value1","key2":"value2"});
    }

    // js 主动调用 oc  方法  ,function JSCallOCMethod2 这个名字无关,这个是JS 绑定的
    function JSCallOCMethod2(){
       //  JSCallOCMethod2 是 oc 注册监听的方法window.webkit.messageHandlers.JSCallOCMethod2.postMessage({"key1":"value1","key2":"value2"});

    }
    
    //OC 主动调用的方法  接收从OC传过来的值,需要OC调用该方法,并传入值
    function OCCallJSMethod(jsonString){
        alert('js获取到的值'+'OCCallJSMethod:'+jsonString);
    }

    //OC 主动调用的方法   jsCallOCReturnJsonStringMethod 这个才是与 OC 交互的方法
    function jsCallOCReturnMethod(){
        //  js 获取 OC 接口返回值  有参数  123123123
        let jsonString = window.prompt("jsCallOCReturnJsonStringMethod","123123123");

        //  js 获取 OC 接口返回值  无参数 
        // let jsonString = window.prompt("jsCallOCReturnJsonStringMethod");
        alert('js获取到的值:'+'jsCallOCReturnJsonStringMethod:'+jsonString);
    }

    </script>
    
</body>
</html>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容