Ziank的技术博客

WKWebView的使用

随着iOS10的出现,大部分应用都已经抛弃了iOS7的支持,那么将UIWebView替换为WKWebView的工作也就提上了日程。毕竟UIWebView的占用内存大,而且存在泄漏问题,包括对js的支持也是不如WKWebView的,所以大部分的UIWebView都应该替换为WKWebView

一、 WKNavigationDelegate由于我们的工程对js只进行了简单的支持和调用,并没有太多复杂的交互,这里就不对WKUIDelegate进行说明了,毕竟我也不是很清楚。这里简单说一下我在替换过程中遇到的一些问题吧:

  1. 弹出的链接替换

    在UIWebViewDelegate中,shouldStartLoadWithRequest方法里可以获取到对应的NSURLRequest,进行处理,然后返回YES/NO;而在WKNavigationDelegate中,对应的方法应该是decidePolicyForNavigationAction,可以在navigationAction中获取到对应的request,然后进行处理,不过没有返回值,而是使用回调方法进行处理。

  1. 初始键盘弹出

    UIWebView中,有一个属性keyboardDisplayRequiresUserAction,设置为NO时就可以在页面刚加载时直接弹出键盘;
    WKWebView中,是没有这个属性的,如果要实现类似的功能,就必须替换WKWebView中相应的方法,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static void (*originalIMP)(id self, SEL _cmd, void* arg0, BOOL arg1, BOOL arg2, id arg3) = NULL;
    void interceptIMP (id self, SEL _cmd, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
    originalIMP(self, _cmd, arg0, TRUE, arg2, arg3);
    }
    void setWkWebViewShowKeybord() {
    Class cls = NSClassFromString(@"WKContentView");
    SEL originalSelector = NSSelectorFromString(@"_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
    Method originalMethod = class_getInstanceMethod(cls, originalSelector);
    IMP impOvverride = (IMP) interceptIMP;
    originalIMP = (void *)method_getImplementation(originalMethod);
    method_setImplementation(originalMethod, impOvverride);
    }

    需要调用函数setWkWebViewShowKeybord,才可以实现该功能;需要特别注意的是,该函数只能调用一次,否则会导致循环调用,程序崩溃。上述代码引用自stackoverflow的答案

  2. JavaScript的调用

    看起来和UIWebView的调用差不多,一个方法是stringByEvaluatingJavaScriptFromString,另一个是evaluateJavaScript,但其实是完全不同的,主要就在于一个是同步调用,另一个是异步调用。
    如果原来的代码已经使用了UIWebView,那么在修改WKWebView时一定要注意,因为异步调用是不可以同时调用多个js方法的,否则执行顺序将无法保证。

    下面写出我把异步调用修改为同步调用的方法,是实现在了WKWebViewcategory里面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - (id)syncEvalJavascriptString:(NSString *)jsCode {
    __block id returnValue = nil;
    __block BOOL finished = NO;
    [self evaluateJavaScript:jsCode completionHandler:^(id _Nullable result, NSError * _Nullable error) {
    returnValue = result;
    finished = YES;
    }];
    while (!finished) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    return returnValue;
    }

    需要注意的就是NSRunLoop那里不可以使用dispatch_semaphore_t信号量代替,会导致永久等待的,已经实测过了。

二、 WKUIDelegate的使用

WKUIDelegate是WebKit对于用户交互的处理代理,它可以使用原生的提示框来代替JavaScript中的提示框,虽然JavaScript中可以做的和原生相似,但是如果有输入的处理的话毕竟还是不如原生的方便。在Delegate中提供了三种提示框的修改:Alert,Confirm,Prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 警告 */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
[[[UIAlertView alloc] initWithTitle:@"警告框" message:message delegate:nil cancelButtonTitle:@"确认" otherButtonTitles: nil] show];
completionHandler();
}
///** 确认框 */
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{
[[[UIAlertView alloc] initWithTitle:@"确认框" message:message delegate:nil cancelButtonTitle:@"确认" otherButtonTitles: nil] show];
completionHandler(1);
}
/** 输入框 */
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
[[[UIAlertView alloc] initWithTitle:@"输入框" message:prompt delegate:nil cancelButtonTitle:@"确认" otherButtonTitles: nil] show];
completionHandler(@"你是谁!");
}

​ > 除此之外,Delegate中还有两个方法:

- `- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;`用于在创建新的WebView时指定配置对象、导航动作对象、window特性。如果没用实现这个方法,不会加载链接,如果返回的是原webview会崩溃。

​ - - (void)webViewDidClose:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);这个方法在关闭webView时进行调用,可以进行环境的清空等操作。

三、 本地资源的加载
​ > WKWebView不支持直接加载bundle中的本地html,如果不进行处理的话,将会导致页面无法正常显示。如果只是需要加载一个单独的html文件,可以直接读取内容,然后使用- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;方法进行加载。但是如果是包含JS文件和css文件的话,就必须使用下面的方法了。

​ > 在iOS9及以上版本的系统中,可以使用方法- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL;进行加载,但是在iOS8系统中,必须把本地资源的内容进行copy,然后再进行加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (NSString *)copyToDocumentPath:(NSString *)urlPath {
NSString *wkWebViewPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"wkWebView"];
if (![[NSFileManager defaultManager] fileExistsAtPath:wkWebViewPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:wkWebViewPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *dstPath = [wkWebViewPath stringByAppendingPathComponent:urlPath];
if (![[NSFileManager defaultManager] fileExistsAtPath:dstPath]) {
NSString *srcPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:urlPath];
[[NSFileManager defaultManager] copyItemAtPath:srcPath toPath:dstPath error:nil];
}
return dstPath;
}

在性能上,替换成了WKWebView之后,性能上确实提高了,比以前流畅多了。