简介
UIWebView 可以在你的应用程序嵌入网页内容并与之交互,但它笨重难用还有内存泄漏且。
WKWebView 代替了UIKit 中的 UIWebView 和 AppKit 中的 WebView,提供了统一的跨双平台 API。它拥有高达60fps的滚动刷新率以及内置手势,在性能、稳定性、功能方面有很大提升,支持了更多的HTML5特性,和 Safari 相同的 JavaScript 引擎。
1. UIWebView
1.1 加载方法
//创建UIWebView
UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
webView.delegate = self;
[self.view addSubview:webView];
//加载URL
NSURL *url = [[NSURL alloc] initWithString:@"http://www.biadu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
加载方法有3种
- (void)loadRequest:(NSURLRequest *)request; //直接装载URL
- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;//加载HTML代码
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;//用于转载本地页面或者外部传来的NSData
1.2 代理回调
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;//进行加载前的预判断,如果返回YES,则会进入后续流程
- (void)webViewDidStartLoad:(UIWebView *)webView;//开始加载网页
- (void)webViewDidFinishLoad:(UIWebView *)webView;//加载完成
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;//加载失败
1.3 Native与JS的相互调用
1.3.1 Native调用JS中的方法
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
//使用方法 JSFunctionName为JS内方法名称 paramete是参数
[self. webView stringByEvaluatingJavaScriptFromString:@"JSFunctionName('paramete')"]
1.3.2 JS调用Native中的方法
JS调用Native方法会在其他线程中调用,所以更新UI则需要放在主线程中进行
#import
可以给类添加JSExport协议,
凡是添加了JSExport协议类我们就可以通过JS调用到其方法变量等。
//此宏定义用于js调用oc方法
#define JSExportAs(PropertyName, Selector) \
@optional Selector __JS_EXPORT_AS__##PropertyName:(id)argument; @required Selector
#endif
例如如下:在js中调用doFoo 即调用了本类的- (void)doFoo:(id)foo 方法
@textblock
@protocol MyClassJavaScriptMethods
JSExportAs(doFoo,
- (NSString *)doFoo:(NSString *)foo
);
@end
@/textblock
//OC中的实现都需要加入 #import
@property(nonatomic,strong)JSContext *jsContext;
//配置JSContext
-(void)configJsContext{
if (_jsContext) return;
_jsContext = [[self contentWebview] valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSExportObject *jsExportObject = [[JSExportObject alloc] init];
jsExportObject.delegate = self;
//把实现JSExport协议的对象添加到JSContext中的native对象doFoo方法 它会自动指定到 -(NSString *)doFoo:(id)foo这个方法当中去 且doFoo可以有NSString返回值
_jsContext[@"native"] = jsExportObject;
}
//在OC中已经添加了native 对象,可以在js中调用native 定义的
var s= {
methodId: i,
params: p
};
native. doFoo(JSON.stringify(s))
2. WKWebView
2.1 加载方法
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
YFMWKWebView *wkWebView = [[YFMWKWebView alloc] initWithFrame:self.view.bounds configuration:config];
wkWebView.navigationDelegate = self;
wkWebView.UIDelegate = self;
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.biadu.com"]];
[wkWebView loadRequest:mutableRequest];
加载方法有4种, 基本与UIWebView相同只是多了一项本地文件加载
//iOS 8
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
//iOS 9
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));
2.2 代理回调
3种代理类型
WKNavigationDelegate( 提供了追踪主窗口网页加载过程和是否进行页面加载新页面的代理方法)
追踪主窗口网页加载过程
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;
是否进行页面加载新页面
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
}
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
decisionHandler(WKNavigationResponsePolicyAllow);
}
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
decisionHandler(WKNavigationActionPolicyAllow);
}
WKUIDelegate(用原生控件显示网页的方法回调)
主要用于WKWebView处理web界面的三种提示框(警告框、确认框、输入框)
//警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
//确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
//输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
WKScriptMessageHandler(提供从网页中收消息的回调方法)
OC中接收JS的调用信息
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"JS 调用了 %@ 方法,传回参数 %@",message.name, message.body);
}
2.3 Native与JS的相互调用
2.3.1 Native调用JS中的方法
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
//使用方法 JSFunctionName为JS内方法名称 paramete是参数
[wkWebView evaluateJavaScript:@"JSFunctionName(‘paramete’)" completionHandler:^(id item, NSError * error) {
}];
2.3.2 JS调用Native中的方法
WKWebView中JS调用Native方法会在主线程中进行
如要进行JS调用Native需要进行WKWebView的配置 方法如下:
Native中需要添加配置信息,最后把配置好的config添加到列表2.1
中。
//创建配置
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
//添加消息处理
WKUserContentController *controller = wkWebView.configuration.userContentController;
[controller addScriptMessageHandler:self name:@"callMe"];
Native向Web中注入自定义的JS代码
在WKUserScript中添加自定义JS代码,然后提交到configuration.userContentController中
//把[configuration.userContentController addUserScript:self.script];添加到列表2.1中
- (WKUserScript *)script {
if (!_script) {
NSString *jsString = @"function JSFunctionName(a) {\
var i = {\
params: a\
};\
return window.webkit.messageHandlers.callMe.postMessage(i);\
}";
// 根据JS字符串初始化WKUserScript对象
WKUserScript *script = [[WKUserScript alloc] initWithSource:jsString
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES];
_script = script;
}
return _script;
}
JS中添加如下方法进行调用
//此方法内的可以写成ScriptMessageHandler中定义的name,为要传的信息内容
window.webkit.messageHandlers..postMessage()
3. 问题解决
一 . 在使用WKWebView时如果你在dealloc打个断点,如果发现没有被释放有可能是引起了循环引用问题(发生在addScriptMessageHandler方法中)
解决方法:
1.自定义一个WKScriptMessageHandler 采用weak方法实现代理方法
2.在离开页面时添加removeScriptMessageHandlerForName这个方法进行移除操作
二. WKWebView JS调用OC 获取返回值问题 因为- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message 此方法没有返回值所以不能实现返回值回调。
方法一: 需要借用到OC调用JS方法逻辑重新调用JS中Callback方法来实现回调 参考本文2.3
或者使用
方法二:可以利用runJavaScriptTextInputPanelWithPrompt回调来实现。
首先注入如下代码,主要实现所有调用OC方法都是通过prompt来实现
//把[configuration.userContentController addUserScript:self.script];添加到列表2.1中
- (WKUserScript *)script {
if (!_script) {
NSString *jsString = @"function JSFunctionName(a) {\
var i = {\
params: a\
};\
return prompt('Native_'+i);\
}";
// 根据JS字符串初始化WKUserScript对象
WKUserScript *script = [[WKUserScript alloc] initWithSource:jsString
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES];
_script = script;
}
return _script;
}
其次在OC中截获Prompt并处理后返回数据
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler {
//如果是调用原生
if (prompt && [prompt hasPrefix:@"Native_"]) {
completionHandler(此处填写处理内容);
}else{
//此处填写自定义promot
}
}```