最近帮了一个舍友将他写的WebAPP整进iOSAPP里面去,做法也很简单,直接一个UIWebView或者WKWebView就行了,然后再处理好缓存。所以我突然想把博客和博客的后台也整合进APP里,遇到了一点小问题,记录一下,方便以后开发遇到相同的问题
WKWebView和UIWebView
因为学习iOS的时间也不是很长,之前虽然有偶尔了解过Hybrid开发,但是一直都是在Native开发。之前只知道iOS有UIWebView这个UI控件可以用来解析Web和展示,这个控件是从iOS3.0之后就有的了,也是长期以来许多Hybrid型APP最为依赖的控件,但是自从iOS8.0以后,苹果又推出了新的WebKit,其中包括了14个类和三个协议。当然这是刚推出的时候,最近的几个系统版本WKWebView一直有在更新。
相比于UIWebView,WKWebView有下面的优点
- 占用更少的内存,个人使用过后内存占用真的低了很多
- 与Safari浏览器相同的JS引擎
- 支持了更多的HTML5特性
- 支持手势返回
- 页面的fps能达到了60,接近Native
从iOS8开始苹果官方就推荐开发者不再使用UIWebView和WebView去加载Web页面,如果APP最低支持到iOS8就应该全面的使用WKWebView去加载Web页面以及进行交互。
WKWebView
WKWebView主要涉及到下面的类或者协议
加载视图
- WKWebView
配置和偏好
- WKWebViewConfiguration
- WKPreferences
页面导航管理
- WKNavigationDelegate
- WKNavigation
- WKNavigationAction
- WKNavigationResponse
- WKBackForwardList
- WKBackForwardListItem
用户界面
- WKUIDelegate
- WKWindowFeatures
脚本注入
- WKUserContentController
- WKScriptMessage
- WKUserScript
缓存数据和持久化
- WKWebsiteDataStore
- WKHTTPCookieStore
- WKHTTPCookieStoreObserver
- WKWebsiteDataRecord
处理WebKit无法处理的URL Scheme类型的资源
- WKURLSchemeHandler
- WKURLSchemeTask
页面结构
- WKFrameInfo
- WKSecurityOrigin
内容拦截
- WKContentRuleList
- WKContentRuleListStore
3D-Touch预览
- WKPreviewElementInfo
- WKPreviewActionItem
- UIPreviewActionItem
Web的简单使用
WKWebView
WKWebView主要用来创建一个展示Web内容的View。
- 使用
initWithFrame:configuration:方法来创建WKWebView对象 - 使用
loadHTMLString:baseURL:方法来加载本地HTML文件 - 使用
loadRequest:方法来加载web内容 - 使用
stopLoading方法来停止加载 - 使用
loading属性来判断网页是否正在加载中 - 使用
WKUIDelegate协议来跟踪网页内容的加载过程
直接上代码
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://sagezhong.top"]]];
self.webView = webView;
[self.view addSubview:self.webView];这样在Controller中就会开始加载一个WKWebView,这个使用方法和UIWebView类似
但是这样直接加载的出来的WKWebView会出现很多问题
WKWebView的一些简单使用细节
手势问题
在UIWebView中实现手势操作一般都是给WebView之注册一个手势然后再用自带的goback方法回到上一级的页面
UISwipeGestureRecognizer *swip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(back)] ;
[self.webView addGestureRecognizer:swip];
// 实现back方法
{
[self.webView goback];
}这样能实现手势返回操作,但是体验感很不好,WKWebView对这个做出了很好的优化,自带有手势操作,而且体验感和Safari浏览器一样。
当我知道WKWebView自带有手势操作之后,马上着手试了一下,结果发现并不能使用手势,后来翻了一下官方的文档,发现WKWebView对象有allowsBackForwardNavigationGestures 这样的一个属性,字面意思就是允许导航手势,而且默认值是NO,所以把这个设置为YES即可,所以一定要仔细阅读官方的文档啊!设置为之后你会发现浏览起网页来和Safari体验感一样了,这点确实很好。
处理_blank跳转的问题
当有一个链接为_blank时,如果是浏览器的话会重现点开一个标签页去渲染这个页面,如果是UIWebView能打开这个链接的网址。但是,在WKWebView中,点击_blank标签的链接会没有反应。
如果是UIWebView只有一个页面是没有问题的,但是WKWebView中提供了新的机制。WKWebView 的 WKNavigationDelegate 有一个方法。
(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;用户点击网页上的链接,需要打开新页面时,将先调用这个方法。这个方法的参数 WKNavigationAction 中有两个属性:sourceFrame和targetFrame,分别代表这个action的出处和目标。类型是 WKFrameInfo 。WKFrameInfo有一个 mainFrame 的属性,正是这个属性标记着这个frame是在主frame里还是新开一个frame。
如果 targetFrame的mainFrame 属性为NO,表明这个 WKNavigationAction 将会新开一个页面。
这个时候WKWebView会调用
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;这个方法返回的是一个WKWebView对象,使用这个方法首先要设置UIDelegate代理,这时候新的Web将会在你返回的WKWebView中渲染出来,需要注意的是不能是同一个WKWebView对象。
但是这样要重新写一个WKWebView对象显得很麻烦,所以可以使用下面这种方法来达到效果
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
WKFrameInfo *frameInfo = navigationAction.targetFrame;
if (![frameInfo isMainFrame]) {
[webView loadRequest:navigationAction.request];
}
return nil;
}简单来说就是判断targetFrame是不是mainFrame,当点击_blank标签链接的时候,此时的targetFrame不是mainFrame,然后给WKWebView重新加载新的请求,这样处理就是在同一个WKWebView上了。
缓存问题
为了提高用户的体验感,需要注意Web的缓存问题,UIWebView的缓存问题比较容易解决,WKWebView的缓存机制有一些不一样,在网上查找了一下,分享一下简单的办法。
首先使用cocoapods引入第三方的库JWNetAutoCache,直接在Podfile中加入
pod `JWNetAutoCache`在执行pod install即可
然后使用NSURLProtocol的分类NSURLProtocol+WKWebVIew
头文件如下
#import <Foundation/Foundation.h>
@interface NSURLProtocol (WKWebVIew)
+ (void)wk_registerScheme:(NSString*)scheme;
+ (void)wk_unregisterScheme:(NSString*)scheme;
@end实现文件如下
#import "NSURLProtocol+WKWebVIew.h"
#import <WebKit/WebKit.h>
//FOUNDATION_STATIC_INLINE 属于属于runtime范畴,你的.m文件需要频繁调用一个函数,可以用static inline来声明。从SDWebImage从get到的。
FOUNDATION_STATIC_INLINE Class ContextControllerClass() {
static Class cls;
if (!cls) {
cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class];
}
return cls;
}
FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() {
return NSSelectorFromString(@"registerSchemeForCustomProtocol:");
}
FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() {
return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:");
}
@implementation NSURLProtocol (WebKitSupport)
+ (void)wk_registerScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = RegisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
+ (void)wk_unregisterScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = UnregisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
@end这个分类出自github一个demo
然后在ViewDidload中执行
[JWCacheURLProtocol startListeningNetWorking];
[NSURLProtocol wk_registerScheme:@"http"];
[NSURLProtocol wk_registerScheme:@"https"];View消失之后记得执行停止监听
[JWCacheURLProtocol cancelListeningNetWorking];处理好缓存问题后就算没有网络也能加载本地的缓存进行浏览,提高了用户的体验感。



?情感共鸣类?