iOS中WebKit框架应用与解析
一、引言
在iOS8之前,在应用中嵌入网页通常需要使用UIWebView这样一个类,这个类通过URL或者HTML文件来加载网页视图,功能十分有限,只能作为辅助嵌入原生应用程序中。虽然UIWebView也可以做原生与JavaScript交互的相关处理,然而也有很大的局限性,JavaScript要调用原生方法通常需要约定好协议之后通过Request来传递。WebKit框架中添加了一些原生与JavaScript交互的方法,增强了网页视图与原生的交互能力。并且WebKit框架中采用导航堆栈的模型来管理网页的跳转,开发者也可以更加容易的控制和管理网页的渲染。关于UIWebView的相关使用,在前面的博客中有详细介绍,地址如下。
UIWebView的使用详解:http://my.oschina.net/u/2340880/blog/469916。
二、WebKit框架概览
WebKit框架中涉及的类很多,框架的设计十分面向对象和模块化,开发者在使用时可以轻松的写出结构清晰的代码。在进行使用前,我们首先应该清楚整个框架的结构和开发思路,下面一张脑图中基本列出了WebKit框架中所涉及到的所有重要的类以及他们之间的相互关系:
如上图所示,WebKit框架中最核心的类应该属于WKWebView了,这个类专门用来渲染网页视图,其他类和协议都将基于它和服务于它。
WKWebView:网页的渲染与展示,通过WKWebViewConfiguration可以进行配置。
WKWebViewConfiguration:这个类专门用来配置WKWebView。
WKPreference:这个类用来进行M相关设置。
WKProcessPool:这个类用来配置进程池,与网页视图的资源共享有关。
WKUserContentController:这个类主要用来做native与JavaScript的交互管理。
WKUserScript:用于进行JavaScript注入。
WKScriptMessageHandler:这个类专门用来处理JavaScript调用native的方法。
WKNavigationDelegate:网页跳转间的导航管理协议,这个协议可以监听网页的活动。
WKNavigationAction:网页某个活动的示例化对象。
WKUIDelegate:用于交互处理JavaScript中的一些弹出框。
WKBackForwardList:堆栈管理的网页列表。
WKBackForwardListItem:每个网页节点对象。
三、使用WKWebViewConfiguration对WebView进行配置
使用下面的代码可以创建一个WKWebView视图,创建WebView视图时,需要使用WKWebViewConfiguration来进行配置:
1 2 3 4
| WKWebView * WK; WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc]init]; WK = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height-40) configuration:config]; [WK loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]]];
|
WKWebViewConfiguration中可以进行配置的方法和属性如下:
1 2 3
| WKProcessPool * pool = [[WKProcessPool alloc]init]; config.processPool = pool;
|
WKProcessPool类中没有暴露任何属性和方法,配置为同一个进程池的WebView会共享数据,例如Cookie、用户凭证等,开发者可以通过编写管理类来分配不同维度的WebView在不同进程池中。
1 2 3 4 5 6 7 8 9
| //进行偏好设置 WKPreferences * preference = [[WKPreferences alloc]init]; //最小字体大小 当将javaScriptEnabled属性设置为NO时,可以看到明显的效果 preference.minimumFontSize = 0; //设置是否支持javaScript 默认是支持的 preference.javaScriptEnabled = YES; //设置是否允许不经过用户交互由javaScript自动打开窗口 preference.javaScriptCanOpenWindowsAutomatically = YES; config.preferences = preference;
|
WKPerference实例为WebView提供一个偏好设置。
1 2 3 4 5 6 7 8 9
| WKUserContentController * userController = [[WKUserContentController alloc]init];
[userController addScriptMessageHandler:self name:@"name"];
NSString *javaScriptSource = @"function userFunc(){window.webkit.messageHandlers.name.postMessage( {\"name\":\"HS\"})}"; WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javaScriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; [userController addUserScript:userScript]; config.userContentController = userController;
|
WKUserContentController专门用来管理native与JavaScript的交互行为,addScriptMessageHandler:name:方法来注册要被js调用的方法名称,之后再JavaScript中使用window.webkit.messageHandlers.name.postMessage()方法来像native发送消息,支持OC中字典,数组,NSNumber等原生数据类型,JavaScript代码中的name要和上面注册的相同。在native代理的回调方法中,会获取到JavaScript传递进来的消息,如下:
1 2 3
| -(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ }
|
WKScriptMessage类是JavaScript传递的对象实例,其中属性如下:
1 2 3 4 5 6 7 8
| @property (nonatomic, readonly, copy) id body;
@property (nullable, nonatomic, readonly, weak) WKWebView *webView;
@property (nonatomic, readonly, copy) WKFrameInfo *frameInfo;
@property (nonatomic, readonly, copy) NSString *name;
|
WKUserContentController实例的addUserScript:用于注入JavaScript代码,后面会专门介绍。
1 2
| config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
|
WebKit框架采用其本身的缓存框架,WKWebsiteDataStore类用来处理数据的存储,其中属性和方法如下:
1 2 3 4 5 6 7 8 9 10
| @interface WKWebsiteDataStore : NSObject
+ (WKWebsiteDataStore *)defaultDataStore;
+ (WKWebsiteDataStore *)nonPersistentDataStore;
@property (nonatomic, readonly, getter=isPersistent) BOOL persistent;
+ (NSSet<NSString *> *)allWebsiteDataTypes; @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| config.suppressesIncrementalRendering = NO;
config.allowsInlineMediaPlayback = YES;
config.allowsAirPlayForMediaPlayback = YES;
config.requiresUserActionForMediaPlayback = NO;
config.allowsPictureInPictureMediaPlayback = YES;
config.selectionGranularity = WKSelectionGranularityCharacter;
config.applicationNameForUserAgent = @"HS";
|
四、WKWebView中的属性和方法解析
下面列举了WKWebView中常用的属性和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| //设置导航代理 @property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate; //设置UI代理 @property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate; //导航列表 @property (nonatomic, readonly, strong) WKBackForwardList *backForwardList; //通过url加载网页视图 - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request; //通过文件加载网页视图 - (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL NS_AVAILABLE(10_11, 9_0); //通过HTML字符串加载网页视图 - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL; //通过data数据加载网页视图 - (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0); //渲染导航列表中的某个网页节点 - (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item; //网页标题 @property (nullable, nonatomic, readonly, copy) NSString *title; //网页的url @property (nullable, nonatomic, readonly, copy) NSURL *URL; //网页是否正在加载中 @property (nonatomic, readonly, getter=isLoading) BOOL loading; //加载进度 可以监听这个属性的值配合UIProgressView来设计进度条 @property (nonatomic, readonly) double estimatedProgress; //是否全部是安全连接 @property (nonatomic, readonly) BOOL hasOnlySecureContent; //证书列表 @property (nonatomic, readonly, copy) NSArray *certificateChain; //是否可以回退 @property (nonatomic, readonly) BOOL canGoBack; //是否可以前进 @property (nonatomic, readonly) BOOL canGoForward; //回退网页 - (nullable WKNavigation *)goBack; //前进网页 - (nullable WKNavigation *)goForward; //刷新网页 - (nullable WKNavigation *)reload; //忽略缓存的刷新 - (nullable WKNavigation *)reloadFromOrigin; //停止加载 - (void)stopLoading; //执行JavaScript代码 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler; //是否允许右滑返回手势 @property (nonatomic) BOOL allowsBackForwardNavigationGestures;
|
WKBackForwardList类为导航管理的网页列表类,其中属性方法意义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @interface WKBackForwardList : NSObject
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *currentItem;
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *forwardItem;
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *backItem;
- (nullable WKBackForwardListItem *)itemAtIndex:(NSInteger)index;
@property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *backList;
@property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *forwardList; @end
|
在WebKit中,网页节点被抽象成为了WKBackForwardListItem类,这个类中封装的属性如下:
1 2 3 4 5 6 7
| @interface WKBackForwardListItem : NSObject
@property (readonly, copy) NSURL *URL;
@property (nullable, readonly, copy) NSString *title;
@property (readonly, copy) NSURL *initialURL;
|
五、关于native与JavaScript交互
WebKit中的native与JavaScript的交互主要有4类。
1.JavaScript调用native方法
这种方式是由WKUserContentController注册,并在代理方法中实现的。
2.native调用JavaScript方法
这种方式通过WKWebView直接调用evaluteJavaScript:completionHandler:方法来实现。
3.将JavaScript代码注入
这种方式可以在网页中注入一些自定义的JavaScript代码,也可以注入自定义的方法,再使用evaluteJavaScript:completionHandler:来调用方法。JavaScript代码的注入也是通过WKUserContentController来完成的,使用addUserScript:方法来注入JavaScript,其中需要通过WKUserScript类来生成要注入的对象,这个类使用如下方法来进行实例化:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
|
4.通过WKUIDelegate来交互
这种方式主要用于相应JavaScript中的弹出框,后面会详细介绍这个协议。
六、WKNavagationDelegate中方法解析
WKNavagationDelegate协议重要有两个作用,监听页面渲染流程与控制页面跳转,其中方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ decisionHandler(WKNavigationActionPolicyAllow); }
-(void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{ NSURLCredential *newCred = [NSURLCredential credentialWithUser:@"" password:@"" persistence:NSURLCredentialPersistenceNone]; [[challenge sender] useCredential:newCred forAuthenticationChallenge:challenge]; completionHandler(NSURLSessionAuthChallengeUseCredential,newCred); }
-(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{ decisionHandler(WKNavigationResponsePolicyAllow); }
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
}
-(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
}
-(void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
}
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
}
-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
}
-(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(nonnull NSError *)error{
}
-(void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
}
|
七、WKUIDelegate协议中方法解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| -(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{ return webView; }
-(void)webViewDidClose:(WKWebView *)webView{
}
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{ NSLog(@"%@",message); completionHandler(); }
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{ NSLog(@"%@",message); completionHandler(YES); }
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{ NSLog(@"%@",prompt); completionHandler(@"123"); }
|
八、扩展
首先,在注册要被JavaScript调用的方法时需要设置代理,在不需要时需要将代理移除,WKUserContentController中也提供了移除这个代理的方法,如果不移除,将会造成WebView不能释放。方法如下:
1 2 3 4
| - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
- (void)removeScriptMessageHandlerForName:(NSString *)name;
|
同样与注入JavaScript对应,也可以将注入的代码移除,方法如下:
1 2 3 4
| - (void)addUserScript:(WKUserScript *)userScript;
- (void)removeAllUserScripts;
|
在上面,经常会见到WKNavagationAction这个类,这个类中封装的是一些页面活动信息,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @interface WKNavigationAction : NSObject
@property (nonatomic, readonly, copy) WKFrameInfo *sourceFrame;
@property (nullable, nonatomic, readonly, copy) WKFrameInfo *targetFrame;
@property (nonatomic, readonly, copy) NSURLRequest *request;
@property (nonatomic, readonly) WKNavigationType navigationType; @end
|
专注技术,热爱生活,交流技术,也做朋友。
——珲少 QQ群:203317592