UIWebView替换WKWebView看这篇就够了

WKWebView替换UIWebView全攻略
本文详细介绍了UIWebView的加载方法、属性、代理及与JavaScript的交互,并探讨了为何需要迁移至WKWebView,对比了两者性能差异。还阐述了WKWebView的特性,包括加载进度条实现、WKNavigationDelegate代理、WKUIDelegate协议,以及如何处理JavaScript与OC的交互,如弹窗、电话识别等常见问题。

一、UIWebView 介绍

1.UIWebView网页加载展现的几种方法


// 使用 NSURLRequest 的方式加载网页 (url可以是远程也可以是本地)
- (void)loadRequest:(NSURLRequest *)request;  

/*! @brief 加载HTML字符串
 *
 * @param string  为要加载的本地HTML字符串
 * @param baseURL 用来确定htmlString的基准地址,相当于HTML的<base>标签的作用,定义页面中所有链接的默认地址
 */
- (void)loadHTMLString:(NSString *)string 
               baseURL:(nullable NSURL *)baseURL;

/*! @brief 以二进制数据的形式加载文件
 *
 * @param Data 文件数据
 * @param MIMEType 文件类型 
 * @param textEncodingName 编码类型
 * @param baseURL 素材资源路径 
 */
 - (void)loadData:(NSData *)data 
         MIMEType:(NSString *)MIMEType 
 textEncodingName:(NSString *)textEncodingName 
          baseURL:(NSURL *)baseURL;
          

2.UIWebView的一些属性和方法


#pragma mark - 判断属性

// 是否可以后退
@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack;
// 是否可以向前
@property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward;
// 是否正在加载
@property (nonatomic, readonly, getter=isLoading) BOOL loading;

#pragma mark - 操作方法

// 刷新网页
- (void)reload;
// 停止加载网页
- (void)stopLoading;
// 后退
- (void)goBack;
// 前进
- (void)goForward;

3.UIWebView的代理方法


//是否允许加载网页,也可获取js要打开的url,通过截取此url可与js交互
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
                                                 navigationType:(UIWebViewNavigationType)navigationType;

//开始加载网页
- (void)webViewDidStartLoad:(UIWebView *)webView;

//网页加载完成
- (void)webViewDidFinishLoad:(UIWebView *)webView;

//网页加载错误
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error; 

4.UIWebView和Javascript之间的交互

主要有两方面 : js执行OC代码、OC调取写好的js代码

(1).OC调取写好的js代码
这里用到UIwebview的一个方法。stringByEvaluatingJavaScriptFromString

示例代码


//  实现自动定位js代码, htmlLocationID为定位的位置 (由js开发人员给出)
NSString *javascriptStr = [NSString stringWithFormat:@"window.location.href = '#%@'",htmlLocationID];
[self.webView stringByEvaluatingJavaScriptFromString:javascriptStr];

//  获取网页的title 
title = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"]

// 获取当前页面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];

(2).js执行OC代码

2.1 js是不能执行OC代码的,但是可以变相的执行,js可以将要执行的操作封装到网络请求里面,然后oc拦截这个请求,获取url里面的字符串解析即可,这里用到代理协议的shouldStartLoadWithRequest函数。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
                                                 navigationType:(UIWebViewNavigationType)navigationType

示例代码

#pragma mark - iOS 拦截UIWebView 内容的点击事件>

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
                                                 navigationType:(UIWebViewNavigationType)navigationType {
    //判断是否是单击
    if (navigationType == UIWebViewNavigationTypeLinkClicked){
        NSString *url = [request.URL absoluteString];
        //拦截链接跳转
        if ([url rangeOfString:@"http:"].location != NSNotFound){
            
            return NO;
        }
    }
    return YES;
}
#pragma mark - UIWebViewNavigationType 类型

UIWebViewNavigationTypeLinkClicked,用户触击了一个链接。
UIWebViewNavigationTypeFormSubmitted,用户提交了一个表单。
UIWebViewNavigationTypeBackForward,用户触击前进或返回按钮。
UIWebViewNavigationTypeReload,用户触击重新加载的按钮。
UIWebViewNavigationTypeFormResubmitted,用户重复提交表单
UIWebViewNavigationTypeOther,发生其它行为。
#pragma mark - 捕获内部webView点击事件

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
                                                 navigationType:(UIWebViewNavigationType)navigationType {
      // 在此捕获当前打开的URL的地址;
      NSLog(@"%@",request.URL.absoluteString);
      // 此内测试在此用webView打开百度页面;点击--新闻--点击新闻分类,当点击新闻为国际时,模拟为注册成功,关闭注册页面;就是关闭当前webView页面
      if ([request.URL.absoluteString hasSuffix:@"internews"]) {
          NSLog(@"line<%d> %s",__LINE__,__func__);
          [self closeView];
         return NO;
     }     
         return YES;
 }
2.2利用NSURLProtocol拦截UIWebView的网络请求

NSURLProtocol它是干什么的呢,是一个挺牛逼的类,它是一个抽象类,不能去实例化它,只能子类化NSURLProtocol,每次在对一个 URL 进行请求的时候 URL Loading System 都会向 已经注册的 Protocol 询问是否可以处理该请求。这里就看出他的作用来了. 比如: 拦截UIWebView的请求,忽略请求,重定向… …

iOS - UIWebView (NSURLProtocol)拦截js、css

二、为什么需要 WKWebView

用 Web 方式承载业务引入 app 的混合开发已成为一个硬性需求,并且随着业务的扩展和审核的限制,该比例会愈加增大,这样对承载的平台就有严格要求,而老旧的 UIWebView 存在严重的性能和内存消耗问题,限制了业务方的自由度。而自 iOS 8 之后,苹果提供的 WebKit 库包含了 WKWebView,WKWebView 采用跨进程方案,Nitro JS 解析器,高达 60fps 的刷新率,理论上性能和 Safari 比肩,而且对 H5 的高度支持,还提供了一个准确的加载进度值属性,我们没有理由拒绝这样的一个新事物。

那么,WKWebview上如何加载进度条呢?

1、添加UIProgressView属性

@property (nonatomic, strong) WKWebView *wkWebView;
@property (nonatomic, strong) UIProgressView *progressView;

2、初始化UIProgressView

- (UIProgressView *)progressView {  
  if(!_progressView)     {  
      _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, self.navBar.maxY, MSWIDTH, 0)];   
      _progressView.progressViewStyle = UIProgressViewStyleBar;      
      _progressView.tintColor = RGB(218, 187, 63);    
      _progressView.trackTintColor = [UIColor clearColor];      
      [self.view addSubview:_progressView];   
    }    
    return _progressView;
}

3、添加kvo监听

[self.wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];

4、计算wkWebView进度条

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change    
                       context:(void *)context {
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        self.progressView.progress = self.wkWebView.estimatedProgress;
        if (self.progressView.progress == 1) {
            __weak typeof (self)weakSelf = self;
            /*添加一个简单的动画,将progressView的Height变为1.4倍,在开始加载网页的代理中会恢复为1.5倍.动画时长0.25s,延时0.3s后开始动画,动画结束后将progressView隐藏*/
            [UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^{
                weakSelf.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.4f);
            } completion:^(BOOL finished) {
                weakSelf.progressView.hidden = YES;
            }];
        }    
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

5、页面开始加载时

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {  
  DLOG(@"%@", webView.URL);   
  [self.progressView setProgress:.15 animated:YES];
}

6、移除监听

    [self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress"];

三、对比 UIWebView 介绍WKWebView

WKWebView 和 UIWebView 二者在使用上差不多(如下代码所示),如果仅做页面呈现和简单交互,不需要考虑太多的投入成本。 对比代码,二者在创建和使用上基本一致,通过一个 URL 字符串构建 Request 对象,然后使用WebView 内置接口载入 Request 对象即可。流程上二者有一定的区别,在节点上,WKWebView 比 UIWebView 多了一个询问过程,在服务器响应请求之后会询问是否载入内容到当前 Frame,在控制上会比UIWebView 粒度更细一些

1.相比UIWebview

1、相比UIWebview支持各种文件格式,WKWebView也支持各种文件格式,并新增了loadFileURL函数,顾名思义加载本地文件。

2、相比UIWebView的一些属性和方法,几乎一样,不同的是有返回值。另外增加了函数reloadFromOrigin和goToBackForwardListItem。


// 会比较网络数据是否有变化,没有变化则使用缓存,否则从新请求。
- (WKNavigation *)reloadFromOrigin; 

// 比向前向后更强大,可以跳转到某个指定历史页面
- (WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item; 

2.一些常用属性

  • allowsBackForwardNavigationGestures : BOOL类型,是否允许左右划手势导航,默认不允许
  • estimatedProgress: 加载进度,取值范围0~1
  • title : 页面title
  • .scrollView.scrollEnabled : 是否允许上下滚动,默认允许
  • backForwardList : WKBackForwardList类型,访问历史列表,可以通过前进后退按钮访问,或者通过goToBackForwardListItem函数跳到指定页面

3.WKWebView 的代理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;

// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction 
                                                     decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

而除此之外,WKWebView 对比 UIWebView 有较大差异的地方有几点。

(1).WKWebView 多了一个重定向通知,在收到服务器重定向消息并且跳转询问允许之后,会回调重定向方法,这点是 UIWebView 没有的,在 UIWebView之上需要验证是否重定向,得在询问方法验证 head 信息。


- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

(2).新增 WKUIDelegate 协议,该协议包含一些UI相关的内容。
在 UIWebView中,Alert、Confirm、Prompt 等视图是直接可执行的,但在WKWebView上,需要通过 WKUIDelegate协议接收通知,然后通过 iOS 原生执行。


// 创建一个新的WebView
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration    
                                                       forNavigationAction:(WKNavigationAction *)navigationAction 
                                                            windowFeatures:(WKWindowFeatures *)windowFeatures;

/// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt 
                                                               defaultText:(nullable NSString *)defaultText 
                                                          initiatedByFrame:(WKFrameInfo *)frame 
                                                         completionHandler:(void (^)(NSString * result))completionHandler;

/// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message 
                                                         initiatedByFrame:(WKFrameInfo *)frame 
                                                        completionHandler:(void (^)(BOOL result))completionHandler;

/// 警告框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message 
                                                       initiatedByFrame:(WKFrameInfo *)frame 
                                                      completionHandler:(void (^)(void))completionHandler;

4.WKWebView 和Javascript之间的交互

主要有两方面 : js执行OC代码、OC调取写好的js代码

(1).OC调取写好的js代码
这个完全依靠 WebView 提供的接口实现,WKWebView 提供的接口和 UIWebView 命名上较为类似,区别是 WKWebView 的这个接口是异步的,而 UIWebView 是同步接口.


#pragma mark - UIWebView
NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    
#pragma mark - WKWebView
[wkWebView evaluateJavaScript:@"document.title"
            completionHandler:^(id _Nullable ret, NSError * _Nullable error) {
      NSString *title = ret;
}];

(2).js执行OC代码
这地方需要两个配置,一个是OC代码的配置,另一个是JS代码的配置,下面先说一下OC代码的配置,细心的小伙伴可能已经发现了,创建WKWebView的时候,除了有 initWithFrame:方法外,还有一个高端的方法 : initWithFrame:configuration:方法。

(2.1).配置 WKWebView


// 创建配置
 WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];

// 创建UserContentController(提供JavaScript向webView发送消息的方法)
WKUserContentController* userController = [[WKUserContentController alloc] init];

// 添加消息处理
//(注意:self指代的对象需要遵守WKScriptMessageHandler协议,结束时需要移除//NativeMethod 这个方法一会要与JS里面的方法写的一样)
[userController addScriptMessageHandler:self name:@"NativeMethod"];

// 将UserConttentController设置到配置文件
config.userContentController = userController ;

// 高端的自定义配置创建WKWebView
WKWebView *webView = [[WKWebView alloc] initWithFrame:
[UIScreen mainScreen].bounds configuration:config];

// 设置访问的URL
NSURL *url = [NSURL URLWithString:@"http://www.jianshu.com"];

// 根据URL创建请求
NSURLRequest *request = [NSURLRequest requestWithURL:url];

// WKWebView加载请求       
[webView loadRequest:request];

// 将WKWebView添加到视图  
[self.view addSubview:webView];

(2.2).实现协议方法
如果JavaScript执行已经写好的:

window.webkit.messageHandlers.NativeMethod.postMessage("就是一个消息啊");

这行代码WKScriptMessageHandler的协议里面userContentController:didReceiveScriptMessage:这个代理方法就会走,并且会有WKScriptMessage的对象,这个WKScriptMessage对象有个name属性,拿到之后你会发现,就是我们注册的NativeMethod这个字符串,这时候你就可以手动调用OC的方法了。如果有多个方法需要调用的话怎么办,看到JavaScript中postMessage()方法有一个参数了没有,可以根据这里的参数来区分调用原生App的哪个方法。


- (void)userContentController:(WKUserContentController *)userContentController 
      didReceiveScriptMessage:(WKScriptMessage *)message {
          // 判断是否是调用原生的
          if ([@"NativeMethod" isEqualToString:message.name]) {
              // 判断message的内容,然后做相应的操作
              if ([@"close" isEqualToString:message.body]) {
              }
          }
   }
   

注意:上面将当前ViewController设置为MessageHandler之后需要在当前ViewController销毁前将其移除(dealloc方法),否则会造成内存泄漏。


[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"NativeMethod"];

5.WKWebView 和Javascript之间的交互注意事项

(1).WKWebview 默认是不弹出js的alert 要想可以弹出alert 需要手动的设置代理实现
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message 
                                                       initiatedByFrame:(WKFrameInfo *)frame 
                                                      completionHandler:(void (^)(void))completionHandler                                                      

具体的实现方法是,我们采用源生的UIAlertController 来实现弹出框,获取js里面的内容并显示出来


- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message 
                                                       initiatedByFrame:(WKFrameInfo *)frame 
                                                      completionHandler:(void (^)(void))completionHandler {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
        NSLog(@"点击了取消按钮==%@",message);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
        NSLog(@"点击了确定按钮==%@",message);
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

(2).WKWebview 默认是不能识别电话号的,这里需要通过实现一个协议来实现拨打电话的功能

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction 
                                                     decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    NSURL *URL = navigationAction.request.URL;
    NSLog(@"获取到URL========%@",URL);
    NSString *scheme = [URL scheme];
    UIApplication *app = [UIApplication sharedApplication];
    // 打电话
    if ([scheme isEqualToString:@"tel"]) {
        if ([app canOpenURL:URL]) {
            [app openURL:URL];
            // 一定要加上这句,否则会打开新页面
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }
   decisionHandler(WKNavigationActionPolicyAllow);
}

(3).WKWebView 默认拦截open.window() 打开新的页面
// WKUIDelegate协议中
- (WKWebView )webView:(WKWebView )webView createWebViewWithConfiguration:(WKWebViewConfiguration )configuration 
                                                     forNavigationAction:(WKNavigationAction )navigationAction 
                                                          windowFeatures:(WKWindowFeatures *)windowFeatures {
// 会拦截到window.open()事件.只需要我们在在方法内进行处理
    if (!navigationAction.targetFrame.isMainFrame) {
      [webView loadRequest:navigationAction.request];
     }
}

(4).WKWebView解决显示字体太小的问题

在使用WKWebView的时候,常常会碰到显示内容比实际css设置的样式不能正常显示,内容普遍的偏小。其实导致这样问题的根源是少了HTML5的meta标签。解决的办法可以在iOS端添加以下的内容,当然也可以让后台添加完整的HTML5的格式。如果要在iOS端指定字体的大小((颜色)也是可以的

(但不推荐在客户端修改字体大小)。
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
   // 修改字体大小 200%
  [webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '200%'" completionHandler:nil];
   // 修改字体颜色  #9098b8
  [webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextFillColor= '#9098b8'" completionHandler:nil];
}

注意:以上属于原创,若有雷同纯属巧合;如有错误,请多多指正,如有遗漏,欢迎大家在评论区补充 😁 转载请标明来源和作者。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值