0%

UIWebView 设置请求头

这篇文章所说的东西是错的,只是一个过程记录,设置请求头请移步NSURLProtocol学习笔记-UIWebView-设置请求头

最近在做内置浏览器的时候遇到了设置请求头的问题,我们项目中要兼容iOS7,所以用的是UIWebView,UIWebView能满足我们的需求,虽然WKWebView性能上比UIWebView要强很多。

设置请求头的方法比较简单,在需要调用loadRequest:方法时,设置NSMutableURLRequestallHTTPHeaderFields的property即可。在webViewDidFinishLoad:代理里面将webView.request.allHTTPHeaderFields的打印出来,该代理第一次执行的时候里面有设置的请求头信息,但是在webView里面点击跳转到另外一个webView里面的时候,会发现webView.request.allHTTPHeaderFields里面是没有第一次设置的请求头信息的。我觉得应该是我loadRequest:的姿势不对,然后把UIWebView Class看了几片,结果里面没有我要的,WTF!

so上面搜了下(keyword: ios webview set global HTTPHeaderField),结果发现可以这样解决

1
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

里面去判断请求头里面是否包含你的请求头,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BOOL headerIsPresent = [[request allHTTPHeaderFields] objectForKey:@"my custom header"]!=nil;
if(headerIsPresent) return YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *url = [request URL];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
// set the new headers
for(NSString *key in [self.customHeaders allKeys]){
[request addValue:[self.customHeaders objectForKey:key] forHTTPHeaderField:key];
}
// reload the request
[self loadRequest:request];
});
});
return NO;

上面代理方法的作用是Sent before a web view begins loading a frame. YES if the web view should begin loading content; otherwise, NO . 即webView加载之前都会走这个代理,需要加载该url内容的话返回YES,否则返回NO。其实我们在这个里面可以做很多事情,比如webView与App原生界面的跳转,js的处理等,我们可以根据自定义url的scheme来判断做响应的处理。其实上面的代码有点逻辑不严谨,在执行GCD之前得判断该url是否为http或者https协议,如果是我们自定义协议的话就没有必要处理,即[request.URL.scheme hasPrefix:@"http"]

好了,这样处理貌似可以解决请求头的问题了,但是当网页里面嵌套url需要显示时(web端称之为iFrame
- webView:shouldStartLoadWithRequest:navigationType:代理方法也会调用那么上面的方法就会造成一个bug,即本来要加载A界面的,结果确保A界面的某个子元素加载处理了。具体的可以下面的两个url,第二个url即为第一个url的iframe。

1
2
http://bajjk.baoan.edu.cn/Default.aspx
http://bajjk.baoan.edu.cn/wnl.html

嗯,那我是不是可以判断该url是不是iFrame,如果不是的话则手动添加请求头。于是又在so (keyword:iOS uiwebview prevent load iframe)找到了方法
在开始加载url那个代理里面通过以下方法判断

1
BOOL isFrame = ![[[request URL] absoluteString] isEqualToString:[[request mainDocumentURL] absoluteString]];

大功告成,可是又来了个问题,假如:A页面有事件可以链接到B页面,B界面也链接到A界面。简单点,在内置webView里面点击导航栏的返回按钮,一般我们是调用- goBack方法返回到上个浏览的页面吧,但是返回的时候上个界面不是iFrame,那么你是不是又要reloadquest一次啊,问题就出在这里啊,canGoBack永远为NO,所以你永远不会离开这个控制器。我们可以在webView的控制器里面增加一个数组,用来在- webViewDidFinishLoad:方法存储已经load的url.absoluteString,在- webView:shouldStartLoadWithRequest:navigationType:里面根据数组里面的url判断是否要重新reloadquest。

好了,经过三个步骤,webView设置请求头已经解决了。哦,==,貌似这方法治标不治本啊,仔细想想,问题不应该是在webView里面,maybe request or url?

在看UIWebView Class Document的时候,学到了新的几点知识。

dataDetectorTypes

By default, a web view automatically converts telephone numbers that appear in web content to Phone links. When a Phone link is tapped, the Phone app launches and dials the number. To turn off this default behavior, set the dataDetectorTypes property with a UIDataDetectorTypes bitfield that does not contain the UIDataDetectorTypePhoneNumber flag.
在webView里面自动识别电话号码,点击后拨打电话,其实我们只要设置webView的dataDetectorTypes属性即可,无需自定义协议。

State Preservation

In iOS 6 and later, if you assign a value to this view’s restorationIdentifier property, it attempts to preserve its URL history, the scaling and scrolling positions for each page, and information about which page is currently being viewed. During restoration, the view restores these values so that the web content appears just as it did before.
很多App会记录webView上次浏览的位置,其实我们可以用viewController的restorationIdentifier属性来处理,appDelegate里面有响应的代理方法
- application:shouldRestoreApplicationState:来处理。我记得以前看到有博客说根据id什么之类的来存储contentOffset来处理该情景,因为没有实践过,不敢乱评论。

allowsLinkPreview

A Boolean value that determines whether pressing on a link displays a preview of the destination for the link.
原来很多App在webView里面长按链接会预览peek,原来是应用了该property啊,当然这样子peep会跳转到Safari,如果想在自己的App里面处理,则需使用SFSafariViewController

stringByEvaluatingJavaScriptFromString:

The stringByEvaluatingJavaScriptFromString: method waits synchronously for JavaScript evaluation to complete. If you load web content whose JavaScript code you have not vetted, invoking this method could hang your app. Best practice is to adopt the WKWebView class and use its evaluateJavaScript:completionHandler: method instead.
原来该方法会等待JavaScript调用完成, 难怪以前在so上面找到方法都是要塞在GCD的异步里面执行。

Managing Media Playback & Managing Pages

虽然这两方面,在项目中没有遇到过,也得知道不,webView在媒体播放以及页面处理中也可以设置相关的属性。