随着iOS10的出现,大部分应用都已经抛弃了iOS7的支持,那么将UIWebView
替换为WKWebView
的工作也就提上了日程。毕竟UIWebView
的占用内存大,而且存在泄漏问题,包括对js的支持也是不如WKWebView
的,所以大部分的UIWebView
都应该替换为WKWebView
。
一、 WKNavigationDelegate
由于我们的工程对js只进行了简单的支持和调用,并没有太多复杂的交互,这里就不对WKUIDelegate
进行说明了,毕竟我也不是很清楚。这里简单说一下我在替换过程中遇到的一些问题吧:
弹出的链接替换
在UIWebViewDelegate中,
shouldStartLoadWithRequest
方法里可以获取到对应的NSURLRequest,进行处理,然后返回YES
/NO
;而在WKNavigationDelegate
中,对应的方法应该是decidePolicyForNavigationAction
,可以在navigationAction中获取到对应的request,然后进行处理,不过没有返回值,而是使用回调方法进行处理。
初始键盘弹出
在
UIWebView
中,有一个属性keyboardDisplayRequiresUserAction
,设置为NO
时就可以在页面刚加载时直接弹出键盘;
在WKWebView
中,是没有这个属性的,如果要实现类似的功能,就必须替换WKWebView
中相应的方法,代码如下:1234567891011121314static 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的答案JavaScript
的调用看起来和
UIWebView
的调用差不多,一个方法是stringByEvaluatingJavaScriptFromString
,另一个是evaluateJavaScript
,但其实是完全不同的,主要就在于一个是同步调用,另一个是异步调用。
如果原来的代码已经使用了UIWebView
,那么在修改WKWebView
时一定要注意,因为异步调用是不可以同时调用多个js
方法的,否则执行顺序将无法保证。下面写出我把异步调用修改为同步调用的方法,是实现在了
WKWebView
的category
里面:123456789101112- (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:
1234567891011121314151617
/* 警告 */- (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,然后再进行加载。
123456789101112131415
- (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
之后,性能上确实提高了,比以前流畅多了。