Web知识
在此之前,最好还是先了解一下,Web开发中,是如何打开新页的。它有两种方式:
<a target='_blank' href='url'/>
window.open(url)
为什么它们行为相似,它们之间必有关联(我查过了:joy:)
结论是target属性指定了在何处打开链接,它可以指定为以下的值
_blank - 在新窗口或tab打开链接
_self - 在当前frame打开链接
_parent
_top
framename
a标签需要明确指定target=_blank,因为它默认是 _self
而window.open()默认 _blank,所以默认是打开新页的。它也可以指定target的,它的完整定义是window.open(URL,target,specs,replace)
那接下来,我们的问题可换为,如何在UIWebView里识别链接的target属性
正文
UIWebView 有一个代理对象,可以在网页加载过程中收到回调,获取一些信息。从UIWebViewDelegate中看看,它有哪些代理方法
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
很快我们就找到了入口
- (BOOL)webView:shouldStartLoadWithRequest:navigationType:
这个方法会在链接加载之前被调用,target判断只能从它的两个参数入手了request
&navigationType
navigationType
是一个枚举值,能识别出是否是超链接、表格提交、前进后退、刷新、表格重复提交,但就是没有target或frame之类的识别😂
全村的希望只能放在request
的身上了,但它只是个标准的http request,根本不会携带frame这种UI层的信息...
好在Google是万能的:joy:,我们还是找到了解决方案:
希望还是在request,我们可以让打开新页的request.url携带上一些特殊的信息...
- 对于a标签,当target=_blank时,修改href,给它拼上一个自定义协议名,例如
<a href='http://example.com'>
↓↓↓↓↓
<a href=' ftnewtab: http://example.com'> - 对于window.open(),重写系统方法,判断target=_blank时,给url拼上同样的自定义协议,然后用location.href方式加载它
这之后,当收到一个链接形如ftnewtab:url
的request时,就可以明确,嗯,这是个要打开新页面的链接
修改URL
接下来且看如何修改 a.href 以及重写 window.open()
这样的事当然是要交给强大的JS了
-
定义JS function
// 修改a.href function MyIPhoneApp_ModifyLinkTargets(newtabScheme) { var allLinks = document.getElementsByTagName('a'); if (allLinks) { var i; for (i=0; i<allLinks.length; i++) { var link = allLinks[i]; var target = link.getAttribute('target'); if (target && target == '_blank') { link.setAttribute('target','_self'); link.href = newtabScheme+':'+escape(link.href); } } } } // 重写window.open function MyIPhoneApp_ModifyWindowOpen(newtabScheme) { window.open = function(url,target,param) { if (url && url.length > 0) { if (!target) target = "_blank"; if (target == '_blank') { location.href = newtabScheme+':'+escape(url); } else { location.href = url; } } } }
-
在WebView加载结束时,注入以及调用这两个函数
// OC Code - (void)webViewDidFinishLoad:(UIWebView *)webView { // 注入js方法 [webView stringByEvaluatingJavaScriptFromString:FTNewTabJSCode]; // 调用js方法 [webView stringByEvaluatingJavaScriptFromString:@"MyIPhoneApp_ModifyLinkTargets('ftnewtab')"]; [webView stringByEvaluatingJavaScriptFromString:@"MyIPhoneApp_ModifyWindowOpen('ftnewtab')"]; }
处理URL
经过JS注入后,发起一个新页请求时,就会得到一个特殊的URL了,当然也需要特殊处理一下
// OC Code
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *URL = request.URL;
if ([URL.scheme isEqualToString:'ftnewtab'] == NO) {
// 不是新开页,正常执行
return YES;
}
NSString *baseURLString = [self stringByEvaluatingJavaScriptFromString:@"document.location.href"];
NSURL *baseURL = [NSURL URLWithString:baseURLString];
NSString *urlString = [[url resourceSpecifier] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
url = [NSURL URLWithString:urlString relativeToURL:baseURL];
[self openNewTabWithURL:url];
return NO;
}
优化
-
实际运行中,
webViewDidFinishLoad
会等到html、css、js、image等所有资源文件下载完成后再调用。而实际上,页面早已部分渲染,此时的a标签已可点击。所以会出现js还未注入却被用户意外点击了开新页的请求。针对这种情况,我们将JS注入提前到了webViewDidStartLoad
- (void)webViewDidStartLoad:(UIWebView *)webView { [webView injectAndExecuteNewTabJSCode]; }