======================cookie===========================================
iOS8 以后推出了 WKWebview,显示更快效率更高.
但是 WKWebview 有几个很坑的问题
1.Cache 与系统分开,并且在 iOS8上面无法清除(iOS9增加了相关方法)
2.Cookie 与系统分开,web 的 cookie 与native 的 cookie 是分开的.
3.不走 NSURLProtocol, 无法自定义网络请求.
So, 需要 native 与 web 统一 cookie 就无从谈起了,甚至 webview 于 webview 之间的 cookie 同步也有问题.
一.先来解决 webview 与 webview 之间的同步问题.
如果使用两个以上 webview 同时访问服务器,这两个 webview 之间的存储是毫无关系的.最关键的是,保存登录状态的 sessionid 也不一样,也就是说,在一个 webview 页面里登录之后,另一个 webview 依旧是未登录的状态.
这个比较容易处理,让两个 webview 使用同一个 WKProcesspool 就可以了.
同一个app中,多个页面使用wkwebview,在一个页面登录,其他页面无需再登录,由于使用wkwebview其cookie不同步,所以会出现在一个页面登录后还需在另一个页面重新登录的情况。上代码吧!
注意:注意:注意:所加载页面必须为【登陆后】的页面。
创建单例类.h
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
@interface WKCookieSyncManager : NSObject
@property (nonatomic, strong) WKProcessPool *processPool;
- (void)setCookie;
+ (instancetype)sharedWKCookieSyncManager;
@end
.m
#import "WKCookieSyncManager.h"
@interface WKCookieSyncManager () <WKNavigationDelegate>
@property (nonatomic, strong) WKWebView *webView;
///用来测试的url这个url是不存在的
@property (nonatomic, strong) NSURL *testUrl;
@end
@implementation WKCookieSyncManager
+ (instancetype)sharedWKCookieSyncManager {
static WKCookieSyncManager *sharedWKCookieSyncManagerInstance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedWKCookieSyncManagerInstance = [[self alloc] init];
});
return sharedWKCookieSyncManagerInstance;
}
- (void)setCookie {
//判断系统是否支持wkWebView
Class wkWebView = NSClassFromString(@"WKWebView");
if (!wkWebView) {
return;
}
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.processPool = self.processPool;
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:self.testUrl];
self.webView.navigationDelegate = self;
[self.webView loadRequest:request];
}
#pragma - get
- (WKProcessPool *)processPool {
if (!_processPool) {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
_processPool = [[WKProcessPool alloc] init];
});
}
return _processPool;
}
- (NSURL *)testUrl {
if (!_testUrl) {
NSURLComponents *urlComponents = [NSURLComponents new];
urlComponents.host = @"oam.yixiangdai.com";
urlComponents.scheme = @"http";
urlComponents.path = @"/tsttsssdsds.aspx";
NSLog(@"测试url=%@", urlComponents.URL);
//一个不存在 的URl
return urlComponents.URL;
}
return _testUrl;
}
//交互js(略过)
#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
//取出cookie
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
//js函数
NSString *JSFuncString =
@"function setCookie(name,value,expires)\
{\
var oDate=new Date();\
oDate.setDate(oDate.getDate()+expires);\
document.cookie=name+'='+value+';expires='+oDate;\
}\
function getCookie(name)\
{\
var arr = document.cookie.match(new RegExp('(^| )'+name+'=([^;]*)(;|$)'));\
if(arr != null) return unescape(arr[2]); return null;\
}\
function delCookie(name)\
{\
var exp = new Date();\
exp.setTime(exp.getTime() - 1);\
var cval=getCookie(name);\
if(cval!=null) document.cookie= name + '='+cval+';expires='+exp.toGMTString();\
}";
//拼凑js字符串
NSMutableString *JSCookieString = JSFuncString.mutableCopy;
for (NSHTTPCookie *cookie in cookieStorage.cookies) {
NSString *excuteJSString = [NSString stringWithFormat:@"setCookie('%@', '%@', 1);", cookie.name, cookie.value];
[JSCookieString appendString:excuteJSString];
NSLog(@"----%@",excuteJSString);
}
//执行js
[webView evaluateJavaScript:JSCookieString completionHandler:nil];
}
使用(在创建wkwebview的时候,其实就两句在横线处标注啦):
//懒加载吧
- (WKWebView *)wkWebView{
if (!_wkWebView) {
///--------------------------把 cookiesManager 做成单例模式,每一个需要同步的 WKWebview 都设置这个 processPool.
WKCookieSyncManager *cookiesManager = [WKCookieSyncManager sharedWKCookieSyncManager];
//设置网页的配置文件
WKWebViewConfiguration * Configuration = [[WKWebViewConfiguration alloc]init];
//允许视频播放
Configuration.allowsAirPlayForMediaPlayback = YES;
// 允许在线播放
Configuration.allowsInlineMediaPlayback = YES;
// 允许可以与网页交互,选择视图
Configuration.selectionGranularity = YES;
/// -------------------------web内容处理池,设置这个processPool
Configuration.processPool = cookiesManager.processPool;
//自定义配置,一般用于 js调用oc方法(OC拦截URL中的数据做自定义操作)
WKUserContentController * UserContentController = [[WKUserContentController alloc]init];
// 是否支持记忆读取
Configuration.suppressesIncrementalRendering = YES;
// 允许用户更改网页的设置
Configuration.userContentController = UserContentController;
_wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 10, 100,100) configuration:Configuration];
//添加到主控制器上
[self.view addSubview:self.wkWebView];
[self webViewloadURLType];//加载的代码(比如下面两行)
//创建一个NSURLRequest 的对象
//NSURLRequest * inforURL = [NSURLRequest requestWithURL:[NSURL URLWithString:selfURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15];////selfURL为登陆后的URl
//加载网页
//[self.wkWebView loadRequest:inforURL];
// 设置代理
_wkWebView.navigationDelegate = self;
_wkWebView.UIDelegate = self;
//开启手势触摸
_wkWebView.allowsBackForwardNavigationGestures = YES;
// 设置 可以前进 和 后退
//适应你设定的尺寸
[_wkWebView sizeToFit];
}
return _wkWebView;
}
======================alert不弹出===========================================
WKWebView默认不响应js的alert()事件,如何可以开启alert权限呢?
设置wkwebview.delegate = self;
实现下面三个方法:
onJsAlert :警告框(WebView上alert无效,需要定制WebChromeClient处理弹出)
onJsConfirm : 确定框.
onJsPrompt : 提示框.
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
// DLOG(@"msg = %@ frmae = %@",message,frame);
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = defaultText;
}];
[alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text?:@"");
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
====================对比UIwebview及其他坑=============
WKWebView使用遇到的坑
简介
使用WKWebView一段时间,发现它和UIWebView的一些区别之处,有一写遇到的坑,现在对处理方式做了个小总结,现分享给大家.
区别
1.EvaluateJavaScript方法为异步
- UIWebview:
在UIWebView
中是同步执行的,直接调用
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
方法返回执行结果 - WKWebView
在WKWebView
中,改为了block
的方式进行值返回,并且该方法的执行是异步的
延伸:执行JS方法的使用场景之一,就是获取当前webview的title,WKWebView
提供了新属性title,如果是想获取title,可以直接使用WKWebView
的title属性.
2.cookie设置方式不同
- UIWebView:
通过该方式设置的,为全局的cookie,项目中任意的UIWebView
均携带一样的cookie.设置之后不需要做额外的操作.
- WKWebView
网页将不再能获取默认的cookie,如果需要携带cookie,需要做一些操作:
1 初始化cookie,NSString *cookie = @"document.cookie='cookieKey=cookieValue'";
2 注入cookie
获取当前的userContentController
:
注入scrpit:
WKUserScript *script = [[WKUserScript alloc] initWithSource:cookieValue injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:script];
注意
注入script时参数indectionTime有两个可选项WKUserScriptInjectionTimeAtDocumentStart
和WKUserScriptInjectionTimeAtDocumentEnd
,
我们看一下官方文档对于这两个选项的解释:
WKUserScriptInjectionTimeAtDocumentStart
: 注入时机为document的元素生成之后,其他内容load之前.
WKUserScriptInjectionTimeAtDocumentEnd
: 注入时机为document全部load完成,任意子资源load完成之前.
一般情况下,如果想尽早注入cookie,在WKUserScriptInjectionTimeAtDocumentStart
时完成即可,但是有一种特殊情况,即目前的诊疗圈为后端渲染,数据请求依赖cookie中的sessionKey
,而前端页面的元素依赖后端返回的数据,因此,有一个问题,即cookie是在页面元素生成之后注入的,而在这之前,后端需要获取cookie,那么应该怎么办呢??
在requestHeader内注入cookie
NSString *cookie = @"cookieKey1=cookieValue1;cookieKey2=cookieValue2";
这样在网络请求开始时,requestHeader将携带cookie.
3.WKWebView默认禁止了一些跳转
- UIWebView
打开ituns.apple.com跳转到appStore, 拨打电话, 唤起邮箱等一系列操作UIWebView默认支持的. - WKWebView
默认禁止了以上行为,除此之外,js端通过window.open()
打开新的网页的动作也被禁掉了.
如何支持呢?
可以跳转appStore或者拨号
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if(webView != self.wkWebView) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
UIApplication *app = [UIApplication sharedApplication];
if ([url.scheme isEqualToString:@"tel"])
{
if ([app canOpenURL:url])
{
[app openURL:url];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
if ([url.absoluteString containsString:@"ituns.apple.com"])
{
if ([app canOpenURL:url])
{
[app openURL:url];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
decisionHandler(WKNavigationActionPolicyAllow);
}
支持window.open()
需要打开新界面是,WKWebView的代理WKUIDelegate
方法
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
会拦截到window.open()事件.
只需要我们在在方法内进行处理
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
================wkWebview==========================
个人所遇到的几个问题记录:
1.cookie问题(多种处理方式,比如新建单例类)。
2.点击内部链接无反应问题。
3.点击不弹出js的alert()。
4.AFNetworking存取cookie。
5.双击某处闪退问题。
6.点击要下载的文件无法下载,跳转到Safari处理。
7.交互的其他问题。
2.点击内部链接无反应问题
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
NSLog(@"createWebViewWithConfiguration");
// <pre>
// a 超连接中target的意思
// _blank -- 在新窗口中打开链接
// _parent -- 在父窗体中打开链接
// _self -- 在当前窗体打开链接,此为默认值
// _top -- 在当前窗体打开链接,并替换当前的整个窗体(框架页)
// </pre>
////********** 点击内部链接无反应问题 ************
//假如是重新打开窗口的话
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
参考:https://www.jianshu.com/p/7e150bf7967f
3.不弹出问题
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
// DLOG(@"msg = %@ frmae = %@",message,frame);
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}])];
[alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = defaultText;
}];
[alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(alertController.textFields[0].text?:@"");
}])];
[self presentViewController:alertController animated:YES completion:nil];
}
======================其他===========================
展示一个网页,但是需要隐藏一部分页面
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
// 在HTML标签都加载完成后,开始处理HTML标签,调用JS,操作document
//移除网页中的footer。
// NSString *doc = @"document.getElementById('footer').remove();";
// [self.wkWebView evaluateJavaScript:doc
// completionHandler:^(id _Nullable htmlStr, NSError * _Nullable error) {
// if (error) {
// NSLog(@"JSError----:%@",error);
// }
// NSLog(@"html----:%@",htmlStr);
// }] ;
//解决长按tabbaritem弹出alert问题。
// NSString *changAnAlertQ = @"document.documentElement.style.webkitTouchCallout='none';";
// [webView evaluateJavaScript:changAnAlertQ completionHandler:^(id _Nullable htmlStr, NSError * _Nullable error) {
//
// }];
}
WKWebView返回某个历史页面
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
//WKWebView返回某个历史页面
if (navigationAction.navigationType==WKNavigationTypeBackForward) {
//判断是返回类型
if (webView.backForwardList.backList.count>0) {
//得到栈里面的list
WKBackForwardListItem * item = webView.backForwardList.currentItem;
//得到现在加载的list
for (WKBackForwardListItem * backItem in webView.backForwardList.backList) {
//循环遍历,得到你想退出到 webView.backForwardList.backList[0]
//添加判断条件
[webView goToBackForwardListItem:backItem];
}
}
}
decisionHandler(WKNavigationActionPolicyAllow);
}
=================================================
来源(cookie):http://blog.csdn.net/ccwf2006/article/details/53173489
来源(alert):http://www.cnblogs.com/n1ckyxu/p/5587722.html
http://blog.csdn.net/j_av_a/article/details/52160413
另外:webview的离线缓存参考http://blog.csdn.net/horisea/article/details/53815596(不适用于WKWebview)。