WKWebView混用https与file资源加载失败的问题

一、问题描述

iOS12系统,在同一个WKWebView先使用( loadRequest:方式)去加载一个在线网页(https://)
再使用(loadFileURL:allowingReadAccessToURL:)加载一个存在Documents目录下的本地的静态html页面(file://)
此时WKWebView将通过代理方法:
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
输出错误:
[ProcessSwapping] 0x10af34160 - ProvisionalPageProxy::didFailProvisionalLoadForFrame: pageID = 1, frameID = 1, navigationID = 4
最终无法正常加载本地的静态html页面。

二、问题原因

同一个WKWebview混用了https://file://两种不同的协议,猜测:iOS12系统中WKWebView有内部限制,会锁定第一次使用资源文件目录(readAccessURL);

例如:先加载网络资源(loadRequest:方式),资源文件目录(readAccessURL)指向网络目录(tmp目录);再去使用(loadFileURL:allowingReadAccessToURL:方式)加载存在Documents目录下的本地的静态html资源时,是无法通过allowingReadAccessToURL:方法重新指定资源文件目录(readAccessURL)。

目前测试其他系统是正常的(iOS11iOS13),iOS9iOS10还有待验证(暂时没设备),仅在iOS12上出现此问题。

三、解决方案

第一种:重新创建一个WKWebView对象,专门用来通过(loadFileURL:allowingReadAccessToURL:)加载本地的静态html页面。

//仅做一个示例
- (void)loadNetworkAndLocalSourceMethod {
    NSURL *httpUrl = [NSURL URLWithString:@"https://www.baidu.com"];
    WKWebView *httpWebView = [[WKWebView alloc] initWithFrame:self.view.bounds];
    httpWebView.navigationDelegate = self;
    httpWebView.UIDelegate = self;
    [self.view addSubview:httpWebView];
    [httpWebView loadRequest:[NSURLRequest requestWithURL:httpUrl]];
    
    NSURL *accessUrl = [NSURL fileURLWithPath:@"xxxxx/xxxx/xx/"];
    NSURL *fileUrl = [NSURL fileURLWithPath:@"xxxxx/xxxx/xx/x.html"];
    WKWebView *fileWebView = [[WKWebView alloc] initWithFrame:self.view.bounds];
    fileWebView.navigationDelegate = self;
    fileWebView.UIDelegate = self;
    [self.view addSubview:fileWebView];
    [fileWebView loadFileURL:fileUrl allowingReadAccessToURL:accessUrl];
}

第二种:将本地的静态html资源存放在工程(NSBundle)中,使用loadRequest:的方式打开,这种方式会增加安装包的大小。

第三种:判断系统版本,如果设备系统是iOS12的系统,将本地的静态html资源的内容,通过代码的方式copytmp目录下,可继续使用loadFileURL:allowingReadAccessToURL:的方式打开本地的静态html页面。

#import <Foundation/Foundation.h>

FOUNDATION_EXPORT NSString *DXDocumentsDirectory(void); //获取Documents路径
FOUNDATION_EXPORT NSString *DXTemporaryDirectory(void); //获取Temporary路径

@interface DXFileManager : NSObject

/*!
 拷贝文件夹或文件从源路径到指定的路径中
 * @param fromPath 源路径
 * @param toPath 目标路径
 */
+ (void)copyFileFromPath:(NSString *)fromPath toPath:(NSString *)toPath;

/*!
 创建一个新的文件夹目录
 * @param directoryPath 文件夹路径
 * @return 是否创建成功
 */
+ (BOOL)createDirectoryAtPath:(NSString *)directoryPath;

/*!
 判断一个文件路径是否是文件夹路径
 * @param targetPath 目标路径
 * @return 是否是文件夹路径
 */
+ (BOOL)directoryExistsAtPath:(NSString *)targetPath;

/*!
 判断一个文件是否是存在
 * @param filePath 文件路径
 * @return 是否是存在文件
 */
+ (BOOL)fileExistsAtPath:(NSString *)filePath;

@end
#import "DXFileManager.h"

//获取Documents路径
NSString *DXDocumentsDirectory(void) {
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject];
};

//获取Temporary路径
NSString *DXTemporaryDirectory(void) {
    return NSTemporaryDirectory();
}


@implementation DXFileManager

+ (void)copyFileFromPath:(NSString *)fromPath toPath:(NSString *)toPath {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *array = [fileManager contentsOfDirectoryAtPath:fromPath error:nil];
    for (int i = 0; i < [array count]; i ++) {
        NSString *component = [array objectAtIndex:i];
        NSString *startPath = [fromPath stringByAppendingPathComponent:component];
        NSString *closePath = [toPath stringByAppendingPathComponent:component];
        
        //判断是否存在路径
        BOOL isFolder = NO; //判断是否是文件夹
        BOOL isExist = [fileManager fileExistsAtPath:startPath isDirectory:&isFolder];
        if (isExist) {
            NSError *error = nil;
            [fileManager copyItemAtPath:startPath toPath:closePath error:&error];
            if (isFolder) {
                [self copyFileFromPath:startPath toPath:closePath];
            }
        }
    }
}

+ (BOOL)createDirectoryAtPath:(NSString *)directoryPath {
    NSError *error = nil;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL status = [fileManager createDirectoryAtPath:directoryPath
                         withIntermediateDirectories:YES
                                          attributes:nil
                                               error:&error];
    return (status && (error == nil));
}

+ (BOOL)directoryExistsAtPath:(NSString *)targetPath {
    BOOL isDirectory = NO;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL status = [fileManager fileExistsAtPath:targetPath isDirectory:&isDirectory];
    return (status && isDirectory);
}

+ (BOOL)fileExistsAtPath:(NSString *)filePath {
    return [[NSFileManager defaultManager] fileExistsAtPath:filePath];
}

@end

第四种:通过本地起服的方式,去加载本地资源html页面资源(需要本地资源html页面支持该方式)。

推荐阅读更多精彩内容