WKWeb​View

WKWeb​View

Mattt Thompson撰写、 Croath Liu翻译、 发布于2014年8月24日

iOS 与 web 之间的关系非常复杂,这种复杂关系甚至可以追溯到几十年前系统建立初期。

其实现在很难说清第一代 iPhone 横空出世是一件多么困难的事情。我们现今司空见惯的触摸屏在当时只是诸多方案中的一种。最早期的产品原型是物理键盘、触摸屏、触控笔的结合,屏幕尺寸才是 5" x 7"。甚至当时 iPod 的轮子都是一个严肃的备选方案。

但最最重要的决定或许都是由软件而非硬件决定的。

iPhone 应该如何运行软件呢?像 OS X 上的应用程序,或者 web 页面,以及 Safari 都应该如何运行起来呢?仿照 OS X 去构建 iPhone OS 的方法已经广泛地被大家熟知了,这种方法到今天也留下了不少争议。

我们来回忆一下 Steve Jobs 2007 年 WWDC keynote 上这句臭名昭著的话:

iPhone 包含了整个 Safari 引擎。因此,你可以创作应用 Ajax 技术的 Web 2.0 应用,表现上和使用上都和 iPhone 原生应用一模一样。这些应用可以和 iPhone 的各种服务完美地集成到一起:这些应用可以有打电话的功能,可以发邮件,可以在 Google Maps 上寻找地标。

Web 一直是 iOS 系统上的二级公民(讽刺的是,其实现今移动网页响应式设计的出现大多是 iPhone 推动的)UIWebView 笨重难用,还有内存泄漏,和 Nirtro JavaScript 引擎谈笑风生的 Safari 不知道要比它高到哪里去了。

然而,这所有的一切都会因为 WKWebView 和 WebKit 框架其他部分的出现而发生改变。


WKWebView 是现代 WebKit API 在 iOS 8 和 OS X Yosemite 应用中的核心部分。它代替了 UIKit 中的 UIWebView 和 AppKit 中的 WebView,提供了统一的跨双平台 API。

自诩拥有 60fps 滚动刷新率、内置手势、高效的 app 和 web 信息交换通道、和 Safari 相同的 JavaScript 引擎,WKWebView 毫无疑问地成为了 WWDC 2014 上的最亮点。

UIWebView & UIWebViewDelegate 这个两个东西是如何在 WKWebKit 中被重构成 14 个类 3 个协议的呢。虽然这次的变化确实带来了不少的新功能,但请一定不要因此感到恐慌!

WKWebKit Framework

Classes

  • WKBackForwardList: 之前访问过的 web 页面的列表,可以通过后退和前进动作来访问到。
    • WKBackForwardListItem: webview 中后退列表里的某一个网页。
  • WKFrameInfo: 包含一个网页的布局信息。
  • WKNavigation: 包含一个网页的加载进度信息。
    • WKNavigationAction: 包含可能让网页导航变化的信息,用于判断是否做出导航变化。
    • WKNavigationResponse: 包含可能让网页导航变化的返回内容信息,用于判断是否做出导航变化。
  • WKPreferences: 概括一个 webview 的偏好设置。
  • WKProcessPool: 表示一个 web 内容加载池。
  • WKUserContentController: 提供使用 JavaScript post 信息和注射 script 的方法。
    • WKScriptMessage: 包含网页发出的信息。
    • WKUserScript: 表示可以被网页接受的用户脚本。 > - WKWebViewConfiguration: 初始化 webview 的设置。
  • WKWindowFeatures: 指定加载新网页时的窗口属性。

Protocols

  • WKNavigationDelegate: 提供了追踪主窗口网页加载过程和判断主窗口和子窗口是否进行页面加载新页面的相关方法。
  • WKScriptMessageHandler: 提供从网页中收消息的回调方法。
  • WKUIDelegate: 提供用原生控件显示网页的方法回调。

UIWebView 和 WKWebView 的 API 区别

WKWebView 继承了 UIWebView 大部分的接口,这让 app 来继承 WKWebKit 也简单了许多(同时随着更新 iOS 8 的越来越多这也成为了某种必需)。

有兴趣的同学可以看一下这两个类的 API 区别:

UIWebView WKWebView
var scrollView: UIScrollView! { get } var scrollView: UIScrollView! { get }
  var configuration: WKWebViewConfiguration! { get }
var delegate: UIWebViewDelegate! var UIDelegate: WKUIDelegate!
  var navigationDelegate: WKNavigationDelegate!
  var backForwardList: WKBackForwardList! { get }

页面加载

UIWebView WKWebView
func loadRequest(request: NSURLRequest!) func loadRequest(request: NSURLRequest!) -> WKNavigation!
func loadHTMLString(string: String!, baseURL: NSURL!) func loadHTMLString(string: String!, baseURL: NSURL!) -> WKNavigation!
func loadData(data: NSData!, MIMEType: String!, textEncodingName: String!, baseURL: NSURL!)  
  var estimatedProgress: Double { get }
  var hasOnlySecureContent: Bool { get }
func reload() func reload() -> WKNavigation!
  func reloadFromOrigin() -> WKNavigation!
func stopLoading() func stopLoading()
var request: NSURLRequest! { get }  
  var URL: NSURL! { get }
  var title: String! { get }

访问历史

UIWebView WKWebView
  func goToBackForwardListItem(item: WKBackForwardListItem!) -> WKNavigation!
func goBack() func goBack() -> WKNavigation!
func goForward() func goForward() -> WKNavigation!
var canGoBack: Bool { get } var canGoBack: Bool { get }
var canGoForward: Bool { get } var canGoForward: Bool { get }
var loading: Bool { get } var loading: Bool { get }

调用 Javascript

UIWebView WKWebView
func stringByEvaluatingJavaScriptFromString(script: String!) -> String!  
  func evaluateJavaScript(javaScriptString: String!, completionHandler: ((AnyObject!, NSError!) -> Void)!)

原生混用

UIWebView WKWebView
var keyboardDisplayRequiresUserAction: Bool  
var scalesPageToFit: Bool  
  var allowsBackForwardNavigationGestures: Bool

页码

WKWebView 目前缺少关于页码相关的 API。

  • var paginationMode: UIWebPaginationMode
  • var paginationBreakingMode: UIWebPaginationBreakingMode
  • var pageLength: CGFloat
  • var gapBetweenPages: CGFloat
  • var pageCount: Int { get }

重构分离开的 WKWebViewConfiguration

下面这些 UIWebView 的属性被重构进了在初始化 WKWebView 传入的设置对象:

  • var allowsInlineMediaPlayback: Bool
  • var mediaPlaybackRequiresUserAction: Bool
  • var mediaPlaybackAllowsAirPlay: Bool
  • var suppressesIncrementalRendering: Bool

JavaScript ↔︎ Swift 对话机制

相对于 UIWebView 最大的提升就是数据在可以 app 和 web 内容之间传递。

使用用户脚本来注入 JavaScript

WKUserScript 允许在正文加载之前或之后注入到页面中。这个强大的功能允许在页面中以安全且唯一的方式操作网页内容。

一个简单的例子如下,用户改变背景的用户脚本被插入到网页中:

Swift
let source = "document.body.style.background = \"#777\";"
let userScript = WKUserScript(source: source, injectionTime: .AtDocumentEnd, forMainFrameOnly: true)

let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)

let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)

WKUserScript 对象可以以 JavaScript 源码形式初始化,初始化时还可以传入是在加载之前还是结束时注入,以及脚本影响的是这个布局还是仅主要布局。于是用户脚本被加入到 WKUserContentController 中,并且以 WKWebViewConfiguration 属性传入到 WKWebView 的初始化过程中。

这个样例可以简单扩展为更为高级的页面修改方法,例如去除广告、隐藏评论等,更复杂的样例见此:让所有出现的"the cloud"变为"my butt"

马杀鸡 魂斗罗(Message Handlers 抱歉我憋了一天,真的不会翻译)

web 和 app 通讯机制也通过 message handler 有很大提升。

就想在Safari 审查元素功能中的 console.log 能在调试终端打印信息一样,网页中的信息也可以通过调用这个函数被传到 app 里:

JavaScript
window.webkit.messageHandlers.{NAME}.postMessage()

这个 API 真正神奇的地方在于 JavaScript 对象可以自动转换为 Objective-C 或 Swift 对象。

Handler 的名字可以通过 WKScriptMessageHandler 协议中的 addScriptMessageHandler() 接口函数设置:

Swift
class NotificationScriptMessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage!) {
        println(message.body)
    }
}

let userContentController = WKUserContentController()
let handler = NotificationScriptMessageHandler()
userContentController.addScriptMessageHandler(handler, name: "notification")

于是当通知进入 app 的时候,比如说在页面中创建一个新对象,相关信息就可以这样传递:

JavaScript
window.webkit.messageHandlers.notification.postMessage({body: "..."});

添加用户脚本来对 web 事件监听并用 Message Handler 将信息传回 app。

同样的方法也可以用来收集页面信息用于 app 的页面展示或数据分析。

例如,如果某人要针对 NSHipster.com 做一个特别的浏览器,就可以加一个能够呼出相似文章列表的按钮:

JavaScriptSwift
// document.location.href == "http://nshipster.com/webkit"
function getRelatedArticles() {
    var related = [];
    var elements = document.getElementById("related").getElementsByTagName("a");
    for (i = 0; i < elements.length; i++) {
        var a = elements[i];
        related.push({href: a.href, title: a.title});
    }

    window.webkit.messageHandlers.related.postMessage({articles: related});
}


如果你的 app 只是对网页内容做了很简单的一层包装,那么 WKWebView 可以彻底改变这种状况。你对于性能和兼容性的所有愿望都将变为现实。所有你想要的东西都在这了。


如果你是一个原生纯粹主义者,你可能会被 iOS 8 新带来强大和扩展性功能吓到。有一个秘密就是,例如 Messages 这种原生应用都应用了 WebKit 来渲染复杂的页面元素。你可能尚且没有意识到,但事实是,webview 在移动开发最佳实践中应该得到一席之地。




//




1、使用UIWebView加载网页

运行XCode 4.3,新建一个Single View Application,命名为WebViewDemo。


2、加载WebView

在ViewController.h添加WebView成员变量和在ViewController.m添加实现

[cpp]  view plain copy
  1. #import <UIKit/UIKit.h>  
  2.   
  3. @interface ViewController : UIViewController  
  4. {  
  5.     UIWebView *webView;  
  6. }  
  7. @end  
[cpp]  view plain copy
  1. ViewController.m  
[cpp]  view plain copy
  1. - (void)viewDidLoad  
  2. {  
  3.     [super viewDidLoad];  
  4.     webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];  
  5.     NSURLRequest *request =[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];  
  6.     [self.view addSubview: webView];  
  7.     [webView loadRequest:request];  
  8. }  
运行,这样百度网页就打开了


手机的网络环境是实时变化的,网络慢的时候,怎么提示用户网页正在打开呢?在网页打开出错的时候怎么提示用户呢?这时候我们就需要知道网页什么时候打开的,

什么时候加载完成,什么时候出错了。那么我们需要实现这个<UIWebViewDelegate>协议

3、实现协议,在ViewController.h修改如下:

[cpp]  view plain copy
  1. #import <UIKit/UIKit.h>  
  2.   
  3. @interface ViewController : UIViewController<UIWebViewDelegate>  
  4. {  
  5.     UIWebView *webView;  
  6. }  
  7. @end  
按住control+command+向上键,切换到ViewController.m文件,这是我们在文件中打入- (void) webView,就能看到如下实现方法:



UIWebView中几个重要的函数
1.- (void )webViewDidStartLoad:(UIWebView  *)webView   网页开始加载的时候调用
2.- (void )webViewDidFinishLoad:(UIWebView  *)webView  网页加载完成的时候调用
3.- (void)webView:(UIWebView *)webView  didFailLoadWithError:(NSError *)error 网页加载错误的时候调用

4、实现这三个方法,加入NSLog。

先在viewDidLoad 的webView实例化下面加上

    [webView setDelegate:self];设置代理。这样上面的三个方法才能得到回调。

三个方法实现如下:

[cpp]  view plain copy
  1. <span style="font-family:Arial, Verdana, sans-serif;color:#333333;">- (void) webViewDidStartLoad:(UIWebView *)webView  
  2. {  
  3.     NSLog(@"webViewDidStartLoad");  
  4. }  
  5. - (void) webViewDidFinishLoad:(UIWebView *)webView  
  6. {  
  7.     NSLog(@"webViewDidFinishLoad");  
  8. }  
  9. - (void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error  
  10. {  
  11.     NSLog(@"didFailLoadWithError:%@", error);  
  12. }  
  13. </span>  
运行打印:

2012-06-23 15:20:29.728 WebViewDemo[1001:f803] webViewDidStartLoad

2012-06-23 15:20:29.991 WebViewDemo[1001:f803] webViewDidFinishLoad

那我们试试error情况,把wifi关掉,运行打印结果:

2012-06-23 15:23:58.939 WebViewDemo[1087:f803] webViewDidStartLoad

2012-06-23 15:23:59.016 WebViewDemo[1087:f803] webViewDidFinishLoad

请求结果不变,为什么关掉网络还成功了呢?缓存?我换163.com试试,这是真正的结果出来了:

2012-06-23 15:24:41.131 WebViewDemo[1134:f803] webViewDidStartLoad

2012-06-23 15:24:41.149 WebViewDemo[1134:f803] didFailLoadWithError:Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo=0x6b41660 {NSErrorFailingURLStringKey=http://www.163.com/, NSErrorFailingURLKey=http://www.163.com/, NSLocalizedDescription=The Internet connection appears to be offline., NSUnderlyingError=0x6eae690 "The Internet connection appears to be offline."}

连接错误了,调用了 didFailLoadWithError。

5、加载等待界面

为了给用户更直观的界面效果,我们加上等待的loading界面试试

在webViewDidStartLoad加入等待

[cpp]  view plain copy
  1. <strong>- (void) webViewDidStartLoad:(UIWebView *)webView  
  2. {  
  3.     //创建UIActivityIndicatorView背底半透明View       
  4.     UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];    
  5.     [view setTag:108];    
  6.     [view setBackgroundColor:[UIColor blackColor]];    
  7.     [view setAlpha:0.5];    
  8.     [self.view addSubview:view];    
  9.       
  10.     activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 32.0f, 32.0f)];    
  11.     [activityIndicator setCenter:view.center];    
  12.     [activityIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhite];    
  13.     [view addSubview:activityIndicator];    
  14.   
  15.     [activityIndicator startAnimating];  
  16.    </strong>  

加载完成或失败时,去掉loading效果

[cpp]  view plain copy
  1. <strong>- (void) webViewDidFinishLoad:(UIWebView *)webView  
  2. {  
  3.     [activityIndicator stopAnimating];  
  4.     UIView *view = (UIView*)[self.view viewWithTag:108];  
  5.     [view removeFromSuperview];  
  6.     NSLog(@"webViewDidFinishLoad");  
  7.   
  8. }  
  9. - (void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error  
  10. {  
  11.     [activityIndicator stopAnimating];  
  12.     UIView *view = (UIView*)[self.view viewWithTag:108];  
  13.     [view removeFromSuperview];  
  14.     </strong>  
运行效果:


//  ViewController.m
//  UIWebView
//
//  Created by xalo on 15/10/13.
//  Copyright © 2015 蓝鸥科技 . All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()< UIWebViewDelegate >

@end

@implementation ViewController

- (
void )viewDidLoad {
    [
super viewDidLoad ];
   
// Do any additional setup after loading the view, typically from a nib.

   
UIWebView *web = [[ UIWebView alloc ] initWithFrame : self . view . bounds ];
   
   
// 数据源
//    - (void)loadRequest:(NSURLRequest *)request;
//    - (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
//    - (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
 
   
// 第一种 :URL
   
NSURLRequest *request = [ NSURLRequest requestWithURL :[ NSURL URLWithString : @"http://www.sina.com.cn" ]];
    [
self . view addSubview :web];
    [web
loadRequest :request];
   
// 第二种 :HTML
//     NSString *HTMLData = @"<img src=\"test2.png\" />ddd";
//    [web loadHTMLString:HTMLData  baseURL:[NSURL fileURLWithPath: [[NSBundle mainBundle]  bundlePath]]];
   
// 第三种 :data
//    NSString *path = [[NSBundle mainBundle] pathForResource:@" 关于 .txt" ofType:nil];
//    NSURL *url = [NSURL fileURLWithPath:path];
//    NSData *data = [NSData dataWithContentsOfFile:path];
//    [web loadData:data MIMEType:@"text/plain" textEncodingName:@"UTF-8" baseURL:nil];

 
    web.
delegate = self ;

}
//    UIWebViewDelegate 中几个重要的函数
//    1.- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
//    2.- (void )webViewDidStartLoad:(UIWebView  *)webView   网页开始加载的时候调用
//    3.- (void )webViewDidFinishLoad:(UIWebView  *)webView  网页加载完成的时候调用
//    4.- (void)webView:(UIWebView *)webView  didFailLoadWithError:(NSError *)error 网页加载错误的时候调用
#pragma mark - <UIWebViewDelegate>
- ( BOOL )webView:( UIWebView *)webView shouldStartLoadWithRequest:( NSURLRequest *)request navigationType:( UIWebViewNavigationType )navigationType{
   
NSLog ( @" 将要开始加载请求 " );
   
return YES ;
}
- (
void )webViewDidStartLoad:( UIWebView *)webView{
   
NSLog ( @" 开始加载 " );
   
// 实现等待界面
   
// 加载背景视图
   
UIView *view = [[ UIView alloc ] initWithFrame : self . view . bounds ];
    view.
tag = 108 ;
    view.
backgroundColor = [ UIColor blackColor ];
    view.
alpha = 0.3 ;
    [
self . view addSubview :view];

   
// 设置加载动画
   
UIActivityIndicatorView *activityIndicator = [[ UIActivityIndicatorView alloc ] initWithFrame : CGRectMake ( 0 , 0 , 30 , 30 )];
    activityIndicator.
tag = 109 ;
    activityIndicator.
center = self . view . center ;
    [activityIndicator
setActivityIndicatorViewStyle : UIActivityIndicatorViewStyleWhite ]; // 加载标识样式有三种
    [view
addSubview :activityIndicator];
    [activityIndicator
startAnimating ]; // 开始动画

}
//webView 结束加载
- (
void )webViewDidFinishLoad:( UIWebView *)webView{
   
NSLog ( @" 结束加载 " );
   
UIActivityIndicatorView *activity = ( UIActivityIndicatorView *)[ self . view viewWithTag : 109 ];
    [activity
stopAnimating ]; // 动画停止
   
UIView *view = ( UIView *)[ self . view viewWithTag : 108 ];
    [view
removeFromSuperview ]; // 移除遮盖视图
}
//webView 加载失败
- (
void )webView:( UIWebView *)webView didFailLoadWithError:( NSError *)error{
   
NSLog ( @" 加载失败 %@" , error);
   
UIActivityIndicatorView *activity = ( UIActivityIndicatorView *)[ self . view viewWithTag : 109 ];
    [activity
stopAnimating ]; // 动画停止
   
UIView *view = ( UIView *)[ self . view viewWithTag : 108 ];
    [view
removeFromSuperview ]; // 移除遮盖视图
}



- (
void )didReceiveMemoryWarning {
    [
super didReceiveMemoryWarning ];
   
// Dispose of any resources that can be recreated.
}

@end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值