iOS - UIWebView & JSContext & WKWebView
UIWebView
A view that embeds web content in your app.
UIWebView 的JS注入
案例 : 移除网页的某些不需要展示的标签
- 准备网页地址 : http://m.dianping.com/tuan/deal/5501525
浏览器终端中演示JS代码删除网页中元素
- 需要处理的网页
— - 网页处理的步骤 ``` javascript 以删除导航为例 : 1.先找到该节点 : var headerTag = document.getElementsByTagName(‘header’)[0]; 2.再找到父节点 : headerTag.parentNode 3.最后用它的父节点删除该节点 : headerTag.parentNode.removeChild(headerTag);
合并: var headerTag = document.getElementsByTagName(‘header’)[0];headerTag.parentNode.removeChild(headerTag);
1
2
3
* 删除导航
``` javascript
var headerTag = document.getElementsByTagName('header')[0];headerTag.parentNode.removeChild(headerTag);
- 删除底部悬停按钮
1
var footerBtnTag = document.getElementsByClassName('footer-btn-fix')[0]; footerBtnTag.parentNode.removeChild(footerBtnTag);
- 删除底部布局
1
var footerTag = document.getElementsByClassName('footer')[0]; footerTag.parentNode.removeChild(footerTag);
- 处理之后的网页

OC调用JS 实现 JS注入
OC和JS的交互需要使用UIWebView的代理方法作为桥梁实现
1
2
3
4
5
6
7
8
9
- (void)viewDidLoad {
[super viewDidLoad];
NSURL * URL = [NSURL URLWithString:@"http://m.dianping.com/tuan/deal/5501525"];
[self.webView loadRequest:[NSURLRequest requestWithURL:URL]];
// 设置代理
self.webView.delegate = self;
}
- 网页加载完时调用的代理方法 ``` objc
- (void)webViewDidFinishLoad:(UIWebView * ) webView; ```
- 网页加载完成之后,调用JS代码的OC方法 ``` objc
- (nullable NSString * )stringByEvaluatingJavaScriptFromString:(NSString * )script; ```
JS注入的具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)webViewDidFinishLoad:(UIWebView * )webView
{
// 拼接JS的代码
NSMutableString * JSStringM = [NSMutableString string];
// 删除导航
[JSStringM appendString:@"var headerTag = document.getElementsByTagName('header')[0];headerTag.parentNode.removeChild(headerTag);"];
// 删除底部悬停按钮
[JSStringM appendString:@"var footerBtnTag = document.getElementsByClassName('footer-btn-fix')[0]; footerBtnTag.parentNode.removeChild(footerBtnTag);"];
// 删除底部布局
[JSStringM appendString:@"var footerTag = document.getElementsByClassName('footer')[0]; footerTag.parentNode.removeChild(footerTag);"];
// OC调用JS代码
[webView stringByEvaluatingJavaScriptFromString:JSStringM];
}
UIWebView监听网页标签的点击(JS调用OC)
案例 : 点击网页某个标签跳转到苹果原生控制器 核心思想 : 拦截webView上所有的网络请求
JS调用OC需要实现的代理方法
1
-(BOOL) webView:(UIWebView * )webView shouldStartLoadWithRequest:(NSURLRequest * )request navigationType:(UIWebViewNavigationType)navigationType;
JS注入给标签添加点击事件
- 网页标签添加点击事件
1
[JSStringM appendString:@"var figureTag = document.getElementsByTagName('figure')[0].children[0]; figureTag.onclick = function(){window.location.href = 'custom://techbird.me'};"];
- 标签的点击事件注入到JS ``` objc
(void)webViewDidFinishLoad:(UIWebView * )webView { // 拼接JS的代码 NSMutableString * JSStringM = [NSMutableString string];
// 删除导航 [JSStringM appendString:@”var headerTag = document.getElementsByTagName(‘header’)[0];headerTag.parentNode.removeChild(headerTag);”]; // 删除底部悬停按钮 [JSStringM appendString:@”var footerBtnTag = document.getElementsByClassName(‘footer-btn-fix’)[0]; footerBtnTag.parentNode.removeChild(footerBtnTag);”]; // 删除底部布局 [JSStringM appendString:@”var footerTag = document.getElementsByClassName(‘footer’)[0]; footerTag.parentNode.removeChild(footerTag);”];
// 给标签添加点击事件 [JSStringM appendString:@”var figureTag = document.getElementsByTagName(‘figure’)[0].children[0]; figureTag.onclick = function(){window.location.href = ‘custom://techbird.me’};”];
1
// OC调用JS代码 [webView stringByEvaluatingJavaScriptFromString:JSStringM]; } ```
- 给标签添加点击事件的目的 : 使标签可点击
- 点击事件发送网络请求的目的 : 可以拦截到标签的点击事件
- 自定义协议的目的 : 给事件设计一个特殊的标记,如果拦截到请求,就通过特殊的标记来区别要做的事情
拦截webView上所有的网络请求,筛选请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
1.JS与OC交互的桥梁
2.可以拦截webView上所有的请求
3.给标签添加点击事件,点击事件主要就是发送请求;发送的请求是自定义协议的,目的是为了做标记.
*/
- (BOOL)webView:(UIWebView * )webView shouldStartLoadWithRequest:(NSURLRequest * )request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(@"%@",request.URL.absoluteString);
// 拿到网页的请求地址
NSString * URLString = request.URL.absoluteString;
// 判断网页的请求地址协议是否是我们自定义的那个
NSRange range = [URLString rangeOfString:@"custom://techbird.me"];
if (range.length > 0) {
// 点击网页中的图片,实现OC原生界面的跳转
TestViewController * VC = [[TestViewController alloc] init];
[self.navigationController pushViewController:VC animated:YES];
return NO;
}
return YES;
}
JSContext
JSContext是JavaScript的运行上下文,他主要作用是执行js代码和注册native方法接口
JSContexts实现OC与JS交互
- 获取上下文
1
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
使用JSContext 实现 JS调用OC
``` objc
(void)viewDidLoad { [super viewDidLoad];
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:@”http://m.dianping.com/tuan/deal/5501525”]]; [self.webView loadRequest:request]; self.webView.delegate = self;
// 获取上下文 JSContext * context = [self.webView valueForKeyPath:@”documentView.webView.mainFrame.javaScriptContext”]; // 监听图片标签点击 context[@”imgtag”] = ^ { [self.navigationController pushViewController:[TestViewController new] animated:YES]; }; // 监听购买标签点击 context[@”buytag”] = ^ { [self.navigationController pushViewController:[Test1ViewController new] animated:YES]; }; } ```
使用JSContext 实现 JS注入
``` objc
(void)webViewDidFinishLoad:(UIWebView * )webView { // 拿到JS的上下文 JSContext * context = [self.webView valueForKeyPath:@”documentView.webView.mainFrame.javaScriptContext”];
// 直接调用JS的函数,还可以向函数里面传入需要的参数.在XCode中向JS中的alert传入需要的message // 拼接JS的代码 NSMutableString * JSStringM = [NSMutableString string];
// 删除导航 [JSStringM appendString:@”var headerTag = document.getElementsByTagName(‘header’)[0];headerTag.parentNode.removeChild(headerTag);”]; // 删除底部悬停按钮 [JSStringM appendString:@”var footerBtnTag = document.getElementsByClassName(‘footer-btn-fix’)[0]; footerBtnTag.parentNode.removeChild(footerBtnTag);”]; // 删除底部布局 [JSStringM appendString:@”var footerTag = document.getElementsByClassName(‘footer’)[0]; footerTag.parentNode.removeChild(footerTag);”];
// 给图片标签添加点击事件 : 自定义协议 [JSStringM appendString:@”var figureTag = document.getElementsByTagName(‘figure’)[0].children[0]; figureTag.onclick = function imgtagclick() {imgtag();};”];
// 给以过期的购买标签重新添加点击事件 [JSStringM appendString:@”var buyBtnTag = document.getElementsByClassName(‘buy-btn btn-gray’)[0]; buyBtnTag.onclick = function buybtnclick() {buytag();};”];
// 执行这个JS代码 [context evaluateScript:JSStringM]; } ```
WKWebView
Starting in iOS 8.0 and OS X 10.10, use WKWebView to add web content to your app. Do not use UIWebView or WebView.
WKWebView的OC和JS交互
- 使用前导入头文件
1
#import <WebKit/WebKit.h> - 遵守代理协议
1
webView.navigationDelegate = self;
代理方法介绍
- 面即将开始加载时调用 (拦截网页的网络请求 : JS调用OC) ``` objc
- (void)webView:(WKWebView * )webView decidePolicyForNavigationAction:(WKNavigationAction * )navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler; ```
- 页面开始加载时调用 ``` objc
- (void)webView:(WKWebView * )webView didStartProvisionalNavigation:(WKNavigation * )navigation; ```
- 收到响应后,决定是否跳转,即是否把这个链接对应的网页加载到WKWebView上 ``` objc
- (void)webView:(WKWebView * )webView decidePolicyForNavigationResponse:(WKNavigationResponse * )navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler; ```
- 当内容开始返回时调用,即服务器已经在向客户端发送网页数据 ``` objc
- (void)webView:(WKWebView * )webView didCommitNavigation:(WKNavigation * )navigation; ```
- 页面加载完成之后调用 (OC调用JS : JS注入) ``` objc
- (void)webView:(WKWebView * )webView didFinishNavigation:(WKNavigation * )navigation; ```
- 页面加载失败时调用 ``` objc
- (void)webView:(WKWebView * )webView didFailProvisionalNavigation:(WKNavigation * )navigation; ```
准备WKWebView
``` objc
(void)viewDidLoad { [super viewDidLoad];
// 创建WKWebView WKWebView * webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self.view addSubview:webView]; webView.backgroundColor = [UIColor redColor]; self.webView = webView;
// 设置代理 self.webView.navigationDelegate = self;
// 加载的网页 NSURL * URL = [NSURL URLWithString:@”http://m.dianping.com/tuan/deal/5501525”]; NSURLRequest * request = [NSURLRequest requestWithURL:URL]; [self.webView loadRequest:request]; } ```
OC调用JS : JS注入 (类似UIWebView)
``` objc // 页面加载完成之后调用
(void)webView:(WKWebView * )webView didFinishNavigation:(WKNavigation * )navigation { // 拼接JS的代码 NSMutableString * JSStringM = [NSMutableString string];
// 删除导航 [JSStringM appendString:@”var headerTag = document.getElementsByTagName(‘header’)[0];headerTag.parentNode.removeChild(headerTag);”]; // 删除底部悬停按钮 [JSStringM appendString:@”var footerBtnTag = document.getElementsByClassName(‘footer-btn-fix’)[0]; footerBtnTag.parentNode.removeChild(footerBtnTag);”]; // 删除底部布局 [JSStringM appendString:@”var footerTag = document.getElementsByClassName(‘footer’)[0]; footerTag.parentNode.removeChild(footerTag);”];
// 给标签添加点击事件 : 自定义协议 [JSStringM appendString:@”var figureTag = document.getElementsByTagName(‘figure’)[0].children[0]; figureTag.onclick = function(){window.location.href = ‘custom://techbird.me’};”];
// OC调用JS代码 [webView evaluateJavaScript:JSStringM completionHandler:nil]; } ```
JS调用OC : (类似UIWebView)
``` objc // 在发送请求之前,决定是否跳转
(void)webView:(WKWebView * )webView decidePolicyForNavigationAction:(WKNavigationAction * )navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { NSLog(@”在发送请求之前,决定是否跳转 decidePolicyForNavigationAction”);
NSString * URLString = navigationAction.request.URL.absoluteString; NSLog(@”监测到的WKWebView上的请求 %@”,URLString);
NSRange range = [URLString rangeOfString:@”custom://”]; if (range.length > 0) {
1 2 3 4 5 6
[self.navigationController pushViewController:[[TestViewController alloc] init] animated:YES]; // 不允许跳转,即不加载这个链接对应的内容 decisionHandler(WKNavigationActionPolicyCancel); } else { // 允许跳转,即加载这个链接对应的内容 decisionHandler(WKNavigationActionPolicyAllow); } } ```
WKWebView 监听加载进度
初始化WKWebView和进度条
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)viewDidLoad {
[super viewDidLoad];
// 创建进度条
self.progress = [[UIProgressView alloc] init];
self.progress.frame = CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 10);
[self.view addSubview:self.progress];
self.progress.progress = 0;
// 创建WKWebView
WKWebView * webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.view addSubview:webView];
webView.backgroundColor = [UIColor redColor];
self.webView = webView;
// 设置代理
self.webView.navigationDelegate = self;
// 加载的网页
NSURL * URL = [NSURL URLWithString:@"http://m.dianping.com/tuan/deal/5501525"];
NSURLRequest * request = [NSURLRequest requestWithURL:URL];
[self.webView loadRequest:request];
// KVO添加进度监听
[webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
KVO监听进度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)observeValueForKeyPath:(NSString * )keyPath ofObject:(id)object change:(NSDictionary * )change context:(void * )context {
if (object == self.webView && [keyPath isEqualToString:@"estimatedProgress"]) {
CGFloat newprogress = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
NSLog(@"进度 %f",newprogress);
if (newprogress != 1.000000) {
// 网页加载时就展示进度
self.progress.hidden = NO;
self.progress.progress = newprogress;
} else {
// 网页加载完成就进度
self.progress.hidden = YES;
}
}
}
WKWebView 其他
WKUIDelegate
- 创建一个新的WebView
1
-(WKWebView * )webView:(WKWebView * )webView createWebViewWithConfiguration:(WKWebViewConfiguration * )configuration forNavigationAction:(WKNavigationAction * )navigationAction windowFeatures:(WKWindowFeatures * )windowFeatures;
- 弹出警告的提示框时调用 ``` objc /**
- 弹出警告的提示框时调用 *
- @param webView 实现该代理的webview
- @param message 警告框中的内容
- @param frame 主窗口
- @param completionHandler 警告框消失调用 */ -(void)webView:(WKWebView * )webView runJavaScriptAlertPanelWithMessage:(NSString * )message initiatedByFrame:(void (^)())completionHandler; ```
- 弹出确认的提示框时调用 ``` objc /**
- 弹出确认的提示框时调用 *
- @param webView 实现该代理的webview
- @param message 确认框中的内容
- @param frame 主窗口
- @param completionHandler 警告框消失调用 */ -(void)webView:(WKWebView * )webView runJavaScriptConfirmPanelWithMessage:(NSString * )message initiatedByFrame:(WKFrameInfo * )frame completionHandler:(void (^)(BOOL result))completionHandler; ```
- 弹出输入提示框时调用 ``` objc /**
- 弹出输入提示框时调用 *
- @param webView 实现该代理的webview
- @param message 确认框中的内容
- @param defaultText 默认的输入框文本信息
- @param frame 主窗口
- @param completionHandler 警告框消失调用 */ -(void)webView:(WKWebView * )webView runJavaScriptTextInputPanelWithPrompt:(NSString * )prompt defaultText:(nullable NSString * )defaultText initiatedByFrame:(WKFrameInfo * )frame completionHandler:(void (^)(NSString * __nullable result))completionHandler; ```
Bug Tips
https://github.com/ShingoFukuyama/WKWebViewTips