wkwebview开发常见问题(wkwebview加载本地沙盒文
1、#pragma mark -https认证
//web项目里面,使用了https认证的问题
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,card);
}
}
2、隐藏状态栏:
NSString *phoneVersion = [[UIDevice currentDevice] systemVersion];
NSArray *versionarr = [phoneVersion componentsSeparatedByString:@"."];
if ([[versionarr objectAtIndex:0] integerValue]<11) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}else{
self.myweb.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//隐藏顶部状态栏,还要设置空间全屏
}
3、禁止WKWebView的手势捏拉缩放
//推选这个方法;这个方法在11.3.1和11.2.6都有效
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
// 禁止放大缩小
NSString *injectionJSString = @"var script = document.createElement('meta');"
"script.name = 'viewport';"
"script.content=\"width=device-width, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\";"
"document.getElementsByTagName('head')[0].appendChild(script);";
[webView evaluateJavaScript:injectionJSString completionHandler:nil];
}
//这个方法在11.3.1无效。11.2.6有效
self.myweb.scrollView.delegate = self;
//禁止手指捏合和放大
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return nil;
}
4、我第一次合作是ionic写的界面。
WKPreferences preferences = [WKPreferences new];
preferences.minimumFontSize = 40.0;
后来另外一个web工程师用vue写的界面。同样的工程加载出来的界面,总是fontSize很大。
我更改 preferences.minimumFontSize = 15.0;解决了这个问题。minimumFontSize大致意思:web的一个px是多大。官网是
/! @abstract The minimum font size in points.
@discussion The default value is 0.
*/
5、弹出系统提示框:
//web界面中有弹出警告框时调用
- (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{
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];
}
6、三种加载html的方式:
- 1、加载远程网页:
NSString *urlstr = @"https://junction.dev.havensphere.com/static/086test1/r003/phone/index.html";
NSURL *url =[NSURL URLWithString:urlstr];
NSURLRequest *request =[NSURLRequest requestWithURL:url];
[self.myweb loadRequest:request];
[self.view addSubview:self.myweb];
- 2、加载项目工程网页:
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKPreferences *preferences = [WKPreferences new];
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 15.0;
configuration.preferences = preferences;
self.myweb = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, Screen_Width, Screen_Height) configuration:configuration];
self.myweb.navigationDelegate = self;
self.myweb.UIDelegate = self;
self.myweb.scrollView.delegate = self;
self.myweb.scrollView.contentSize= CGSizeMake(Screen_Width, Screen_Height);
self.myweb.scrollView.bounces = false;
NSString *bundleFile = [[NSBundle mainBundle] pathForResource:@"HTML" ofType:@"bundle"];
NSString *path = [bundleFile stringByAppendingString:@"/dist/index.html"];
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSURLRequest *request = [NSURLRequest requestWithURL:fileURL];
[self.myweb loadRequest:request];
[self.view addSubview:self.myweb];
NSString *phoneVersion = [[UIDevice currentDevice] systemVersion];
NSArray *versionarr = [phoneVersion componentsSeparatedByString:@"."];
if ([[versionarr objectAtIndex:0] integerValue]<11) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}else{
self.myweb.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//隐藏顶部状态栏,还要设置空间全屏
self.mywebui.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//隐藏顶部状态栏,还要设置空间全屏
}
//点击web界面跳转时候执行的方法
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
NSLog(@"createWebViewWithConfiguration");
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
// [MBProgressHUD showInView:self.view message:@"正在加载网页!!"];
[self.activityIndiactorView startAnimating];
NSLog(@"didStartProvisionalNavigation");
}
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
decisionHandler(WKNavigationResponsePolicyAllow);
NSLog(@"decidePolicyForNavigationResponse");
self.responsecount++;
if (self.responsecount==7) {//处理加载时间很长的时候的加载问题
[self.activityIndiactorView stopAnimating];
}
return;
}
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
self.responsecount = 0;
NSLog(@"didCommitNavigation");
}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
[self.activityIndiactorView stopAnimating];
NSLog(@"didFinishNavigation");
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
[self.activityIndiactorView stopAnimating];
NSLog(@"didFailProvisionalNavigation");
}
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"didReceiveServerRedirectForProvisionalNavigation");
NSLog(@"%s", __FUNCTION__);
}
- 3从接口下载压缩包,解压缩再加载出来。(本地沙盒目录指这几个:Library/Preferences、Library/Caches、Documents)
//请求当前项目压缩包版本号。如果版本号比本地版本号大或者本地没有,请求zip压缩包;如果版本号和本地一样,直接加载本地web数据。
-(void)getversion{
[MBProgressHUD showInView:self.view indicatorMessage:@"正在加载网页版本数据" duration:-1];
// 1.获得请求管理者
AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
// 2.申明返回的结果是text/html类型
mgr.responseSerializer = [ AFJSONResponseSerializer serializer];
// 3.发送GET请求
[mgr GET:@"http://101.132.91.12:8681/iot/app/info?id=1" parameters:nil progress::^(NSProgress * _Nonnull downloadProgress) {
NSLog(@"%@",downloadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSDictionary *dic = (NSDictionary *)responseObject;
NSString *version = [[[dic objectForKey:@"content"]objectAtIndex:0]objectForKey:@"versioncode"];
NSString *defaultsversion = [[NSUserDefaults standardUserDefaults] objectForKey:@"versioncode"];
self.defaultsversion = version;
if (defaultsversion==nil) {
[MBProgressHUD showInView:self.view indicatorMessage:@"请求云端数据,请稍等" duration:-1];
[self rquestZipArchivePath:@"http://101.132.91.12:8681/iot/file/app/iot.zip"];
return ;
}
if ([version intValue]>[defaultsversion intValue]) {
[MBProgressHUD showInView:self.view indicatorMessage:@"请求云端数据,请稍等" duration:-1];
[self rquestZipArchivePath:@"http://101.132.91.12:8681/iot/file/app/iot.zip"];
return ;
}
NSArray *documentArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *path1 = [[documentArray lastObject] stringByAppendingPathComponent:@"Preferences"];
if([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/iot",path1]])
{
[self loadWebData];
}else{
[MBProgressHUD showInView:self.view indicatorMessage:@"请求云端数据,请稍等" duration:-1];
[self rquestZipArchivePath:@"http://101.132.91.12:8681/iot/file/app/iot.zip"];
}
NSLog(@"responseObject:%@",version);
return ;
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[MBProgressHUD hideHUDForView:self.view];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"本地网页版本不存在,请重启软件程序!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
[alert show];
NSLog(@"error:%@",error);
}];
}
#pragma mark 请求zip地址
//请求到的压缩包数据,进行解压
-(void)rquestZipArchivePath:(NSString *)pathUrl{
//远程地址
NSURL *URL = [NSURL URLWithString:pathUrl];
//默认配置
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
//请求
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask * downloadTask= [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
//- block的返回值, 要求返回一个URL, 返回的这个URL就是文件的位置的路径
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//再次之前先删除本地文件夹里面相同的文件夹
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *contents = [fileManager contentsOfDirectoryAtPath:cachesPath error:NULL];
NSEnumerator *e = [contents objectEnumerator];
NSString *filename;
NSString *extension = @"zip";
while ((filename = [e nextObject])) {
if ([[filename pathExtension] isEqualToString:extension]) {
[fileManager removeItemAtPath:[cachesPath stringByAppendingPathComponent:filename] error:NULL];
}
}
NSString *path = [cachesPath stringByAppendingPathComponent:response.suggestedFilename];
return [NSURL fileURLWithPath:path];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (error==nil) {
//设置下载完成操作
// filePath就是你下载文件的位置,你可以解压,也可以直接拿来使用
NSString *htmlFilePath = [filePath path];// 将NSURL转成NSString
NSArray *documentArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *path = [[documentArray lastObject] stringByAppendingPathComponent:@"Preferences/"];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:[NSString stringWithFormat:@"%@/iot",path] error:nil];
[self releaseZipFilesWithUnzipFileAtPath:htmlFilePath Destination:path];
return ;
}
[MBProgressHUD hideHUDForView:self.view];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"网页数据下载失败,请重启软件程序!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
[alert show];
}];
[downloadTask resume];
}
#pragma mark 解压
//把压缩包数据解压到本地后,要想加载出来,必须把document数据复制到Preferences文件夹
- (void)releaseZipFilesWithUnzipFileAtPath:(NSString *)zipPath Destination:(NSString *)unzipPath{
NSError *error;
if ([SSZipArchive unzipFileAtPath:zipPath toDestination:unzipPath overwrite:YES password:nil error:&error delegate:self]) {
NSString *path = [unzipPath stringByAppendingString:@"/iot"];
NSFileManager *fileManager1 = [NSFileManager defaultManager];
NSString *tmpPath2 = NSTemporaryDirectory();
if ([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@iot",tmpPath2]]){
[fileManager1 removeItemAtPath:[NSString stringWithFormat:@"%@iot",tmpPath2] error:nil];
}
[fileManager1 copyItemAtPath:[NSString stringWithFormat:@"%@/iot",path] toPath:[NSString stringWithFormat:@"%@iot",tmpPath2] error:nil];
[[NSUserDefaults standardUserDefaults] setObject:self.defaultsversion forKey:@"versioncode"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self loadWebData];
// NSLog(@"解压缩成功!");
}
else{
[MBProgressHUD hideHUDForView:self.view];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"解压数据失败,请重启软件程序!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
[alert show];
}
}
//加载本地沙盒目录下的index.html(本地沙盒目录指这几个:Library/Preferences、Library/Caches、Documents)
-(void)loadWebData{
NSArray *documentArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *path1 = [[documentArray lastObject] stringByAppendingPathComponent:@"Preferences"];
NSFileManager *fileManager1 = [NSFileManager defaultManager];
if([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/iot",path1]])
{
//第一步:找到文件所在目录
NSArray *LibraryArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *CachesPath = [[LibraryArray lastObject] stringByAppendingPathComponent:@"Caches"];
NSString *indexPath = [CachesPath stringByAppendingPathComponent:@"/dist/index.html"];
//第二步:创建加载URL和访问权限URL(加载URL:访问的初始文件一般是index;访问权限URL:所有html、js、资源文件所在的文件夹一般是项目文件名字)。注意如果没有访问权限URL,html没办法加载相关的js和资源文件。
//创建加载URL方法一:
// indexPath=[[NSURL fileURLWithPath:indexPath]absoluteString];
// NSURL *loadurl =[NSURL URLWithString:indexPath];//URLWithString后面的path1必须前面有file:///,[[NSURL fileURLWithPath:path1]absoluteString]这个处理可以得到file:///
//创建加载URL方法二:
NSURL *loadurl =[NSURL fileURLWithPath:indexPath];//fileURLWithPath后面跟的是文件目录不需要file:///
//创建访问权限URL
NSString *accessURLStr = [[[LibraryArray lastObject] stringByAppendingPathComponent:@"Caches"] stringByAppendingPathComponent:@"/dist"];
NSURL *accessURL = [NSURL fileURLWithPath:accessURLStr];
//第三步:进行加载
[self.myweb loadFileURL:loadurl allowingReadAccessToURL:accessURL];
[MBProgressHUD showInView:self.view successMessage:@"网页加载成功!"];
}else{
[MBProgressHUD hideHUDForView:self.view];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"本地网页配置文件不存在,请重启软件程序!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK",nil];
[alert show];
}
}
注意:
1、解压缩功能要导入SSZipArchive库(包含minizip和aes),导入后出现了20多个错误,解决办法:
选择所有.c文件,将属性的 identity and type 改为Objective-C Source
2、下载功能需要导入AFNetworking库。
3、加载本地沙盒功能主要针对文件夹里面除了html还有js和其它资源文件,如果只有一个html文件,那accessURL就是loadURL。这里沙盒目录主要指:Library/Preferences、Library/Caches、Documents(建议使用Library/Caches)。
7、js和原生交互:
参考文章:iOS下JS与OC互相调用(六)--WKWebView + WebViewJavascriptBridge
1、引入工作:WebViewJavascriptBridge文件夹
@property WKWebViewJavascriptBridge *webViewBridge;
2、创建WebViewJavascriptBridge实例。
_webViewBridge = [WKWebViewJavascriptBridge bridgeForWebView:self.webView];
// 如果控制器里需要监听WKWebView 的`navigationDelegate`方法,就需要添加下面这行。
[_webViewBridge setWebViewDelegate:self];
3、iOS语法:web调用iOS的方法:
[_webViewBridge registerHandler:@"locationClick" handler:^(id data, WVJBResponseCallback responseCallback) {
NSDictionary *tempDic = data;
NSLog(@"%@",tempDic);
// 获取位置信息
NSString *location = @"广东省深圳市南山区学府路XXXX号";
// 将结果返回给js
responseCallback(location);//responseCallback(nil);
}];
注意:
(1)、js可以传数字给iOS,iOS通过[data isKindOfClass:[NSNumber class]]判断是不是数字类型
(2)、iOS返回可以是数字类型如@1。
(3)、无返回的情况
js方法:
WebViewJavascriptBridge.callHandler('colorClick',params);
iOS方法:
[_webViewBridge registerHandler:@"colorClick" handler:^(id data, WVJBResponseCallback responseCallback) {
// data 的类型与 JS中传的参数有关
NSDictionary *tempDic = data;
CGFloat r = [[tempDic objectForKey:@"r"] floatValue];
CGFloat g = [[tempDic objectForKey:@"g"] floatValue];
CGFloat b = [[tempDic objectForKey:@"b"] floatValue];
CGFloat a = [[tempDic objectForKey:@"a"] floatValue];
weakSelf.webView.backgroundColor = [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a];
}];
(4)、传入数字类型时候,如果用数字类型作为key去找[[NSUserDefaults standardUserDefaults] objectForKey:key],会导致崩溃。
4、iOS语法:iOS调用web方法:
* (1)、传字符串给js,所以如果是json或者array都需要转换成字符串传递。
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSData *jsonData= [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString *jsStr = [NSString stringWithFormat:@"backMQTTData('%@')",jsonString];
[self.myweb evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {}];//wkwebview执行方法
* (2)、传数字给js。(传bool类型时候数字为0或者1就可以)
NSString *jsStr = [NSString stringWithFormat:@"iOSCallJS(%@)",@0];
[self.myweb evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {}];//wkwebview执行方法
alert(typeof(istrue));//number
alert(istrue);
5、js语法:
if (window.AndroidWebView) {
}else if(window.WebViewJavascriptBridge){
WebViewJavascriptBridge.callHandler(funName,iosKey,function(response) {
if(methodType==APPCONFIG.METHOD_GET){
callback(response);
}
});
}
注意:调用安卓方法时候是顺序执行的,而调用iOS的时候顺序执行完成后,在回调到callHandler里面的。