带你走进WKWebView的世界

通过学习,你将会学习以下几个方面的内容:

  • **什么是WKWebView以及它和UIWebView的区别是什么 **
  • **认识SafariServices.framework框架,捎带认识UIActivity **
  • 回顾UIWebView在公司工程中的使用
  • WKKit 和 WKWebView简介,特性认识API
  • UIWebView 和WKWebview 以及JSCore JS和OC的相互调用
  • NSURLProtocal的使用
  • WKWebView的一些坑

文章结构目录

1.背景
2.移动端展示web方案

--- 2.1 Safari
--- 2.2 SFSafariViewController
--- 2.3 UIWebView,回顾在公司工程中的使用
--- 2.3 UIWebView 接口说明

3.WKKit& WKWebView

--- 3.1WKKit框架介绍,WKWebView API介绍
--- 3.2OC如何给JS注入对象及JS如何给IOS发送数据
--- 3.3JS调用alert、confirm、prompt时,不采用JS原生提示,而是使用iOS原生来实现
--- 3.4如何监听web内容加载进度、是否加载完成
--- 3.5如何处理去跨域问题
--- 3.6JS和OC的相互调用
--- 3.7WKWebview API问题

1.背景

自iOS问世以来,iOS 和web总是形影相随。毕竟,当iOS诞生的时候,网站已经有15年的历史了(第一个网站的创建甚至可以追溯到1991年,而iOS在2007年才出现,而那时候还没有AppStore,没有应用分发市场)iPhone出现的时候就已经有很多的web内容了,这样iOS必须提供一种方法来显示web内容。
----翻译一位苹果工程师的一段话

2.移动端展示web方案

在iOS 9中,我们有不同的渲染网页内容的选择,根据具体需求,开发人员可以评估不同的选择,选择一个适合他们更好。让我们看看一个开发人员可以采用的不同的解决方案。

让我们看看目前在移动端都有哪些方案:

  • Safari
  • SFSafariViewController
  • UIWebView
  • WKWebview

对于前两种我们都已了解或者掌握,我们先简单的说明一下,我们的重心还是WKWebview上。

2.1Safari

我们通常用

 NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
 [[UIApplication sharedApplication] openURL:url];

这种简单的方式用苹果自带的safari浏览器打开一个网页,从iOS9开始从一个app打开另一个app就像内嵌app一样,可以返回原来的app. 虽然这是一个小小的方案,但是对于跳转到其他app,大家还是不怎么情愿的,因为,我们只想让用户停留在我们的app里。

2.2SFSafariViewController

我们不得不说safari确实很强大,在iOS9,苹果公司给我们提供了一个SafariServices.framework ,这个框架很简单,就几个文件
1.SFSafariViewController(浏览控制器)
2.SSReadingList(用户safari阅读列表)
3.SFContentBlockerState(safari内容拦击)不用了解

这里只是简单了解下SFSafariViewController,这个控制器的API,很简单。
用SFSafariViewController 这种方式可以加载web内容,而且是内置在我们app里的浏览器,但是这个控制器有个致命的弱点就是不能自定义,苹果公司并没有开放出很多API,有一些私有的API可以获得进行自定义,但是我们不能使用,略显尴尬,我想之所以苹果公司不开放很多api的理由,应该主要还是以原生app和safari为主流的原因吧。

2.3UIWebView

废话不多说,我们把重点放在最后WkWebview上,再认识WKWebview之前,我们有必要再简单回顾下UIwebView的使用,以及我们工程里对UIwebview的使用.
UIWebview是UIKit框架里的一个视图类, 继承UIView, 整个接口文件也只有100行,里面有一个scrollview的属性,这些都是我们简单的认识。大家感兴趣可以了解下UIwebView的内部实现,后面稍微提一下。

1.公司对UIWebview的简单使用
1.1用webView打电话
代码如下



   NSString *phoneNum = nil;
   if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0){
            phoneNum = [NSString stringWithFormat:@"telprompt://%@",self.phoneNumber];
        } else {
            phoneNum = [NSString stringWithFormat:@"tel://%@",self.phoneNumber];// 电话号码
    }
 

  if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNum]];            
    } else {
    UIWebView *callPhoneWebVw = [[UIWebView alloc] initWithFrame:CGRectZero];
   self.callwebView = callPhoneWebVw;
     NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:phoneNum]];
     [_callwebView loadRequest:request];
 }

1.2 封装了 PZLWJSBridgeView ,控件继承UIWebview
在家居和知识详情模块,都有用到这个类
1.3LLLwebviewcontroller大家都知道,我们最多的是使用这个类,加载wap页面,我们在这里简单看下这个控制器是怎么使用的。
其实使用也是蛮简单的,具体步骤如下:
1.3.1创建UIwebview(此处省略代码)
1.3.2创建假的进度条,之所以是假的,是因为webview不支持加载进度
1.3.3然后就是一些分享业务上的逻辑了(简单调用js代码获取js数据)
1.3.4 每次请求的时候有添加cookie的操作,然后dealloc的时候删除 cookie(UIwebview并不支持cookie操作)
1.3.5 加载URL过滤,PZLWURLRequestFilter: NSURLProtocol,咱公司对于这个继承自NSURLProtocol的文件也打成了静态库,感兴趣的同学可以去github上找一下, github有实现Demo。但是这个URL过滤,在WKWebView上不支持。
我们简单学习一下这个在我们公司封装成静态库的实现过程吧

我们用UIWebview无非就是三个代理和调用javascript代码,而且只是获得html的内容。像这样 :

[self.webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('soufunclient').innerHTML"];

总的来说,UIwebview的使用很简单,这里对UIWebview头文件做了说明

//  Copyright (c) 2007-2015 Apple Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIView.h>
#import <UIKit/UIKitDefines.h>
#import <UIKit/UIDataDetectors.h>
#import <UIKit/UIScrollView.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, UIWebViewNavigationType) {
    UIWebViewNavigationTypeLinkClicked,//用户触发了一个链接
    UIWebViewNavigationTypeFormSubmitted,//用户提交了一个表单
    UIWebViewNavigationTypeBackForward,//用户触击前进前进或返回按钮
    UIWebViewNavigationTypeReload,//用户触击重新加载的按钮
    UIWebViewNavigationTypeFormResubmitted,//用户重复提交表单
    UIWebViewNavigationTypeOther//发生了其他行为
} __TVOS_PROHIBITED;

//2. 加载内容关于分页显示几种不同类型
typedef NS_ENUM(NSInteger, UIWebPaginationMode) {
    UIWebPaginationModeUnpaginated,
    UIWebPaginationModeLeftToRight,
    UIWebPaginationModeTopToBottom,
    UIWebPaginationModeBottomToTop,
    UIWebPaginationModeRightToLeft
} __TVOS_PROHIBITED;

typedef NS_ENUM(NSInteger, UIWebPaginationBreakingMode) {
    UIWebPaginationBreakingModePage,//默认设置是这个属性,CSS属性以页样式。
    UIWebPaginationBreakingModeColumn//当UIWebPaginationBreakingMode设置这个属性的时候,这个页面内容CSS属性以column-break 代替page-breaking样式。
} __TVOS_PROHIBITED;

@class UIWebViewInternal;
@protocol UIWebViewDelegate;

NS_CLASS_AVAILABLE_IOS(2_0) __TVOS_PROHIBITED @interface UIWebView : UIView <NSCoding, UIScrollViewDelegate> 

@property (nullable, nonatomic, assign) id <UIWebViewDelegate> delegate;

@property (nonatomic, readonly, strong) UIScrollView *scrollView NS_AVAILABLE_IOS(5_0);

- (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;

@property (nullable, nonatomic, readonly, strong) NSURLRequest *request;

- (void)reload;
- (void)stopLoading;

- (void)goBack;
- (void)goForward;

@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack;
@property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward;
@property (nonatomic, readonly, getter=isLoading) BOOL loading;

- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

//是否让内容伸缩至适应屏幕当前尺寸
@property (nonatomic) BOOL scalesPageToFit;

//这个属性如果设置为YES,当进入到页面视图可以自动检测电话号码,让用户可以单机号码进行拨打,不过现已弃用。
@property (nonatomic) BOOL detectsPhoneNumbers NS_DEPRECATED_IOS(2_0, 3_0);

//这个属性可以设定使电话号码,网址,电子邮件和符合格式的日期等文字变为连接文字。
@property (nonatomic) UIDataDetectorTypes dataDetectorTypes NS_AVAILABLE_IOS(3_0);

//这个属性决定了页面用内嵌HTML5播放视频还是用本地的全屏控制。为了内嵌视频播放,不仅仅需要在这个页面上设置这个属性,还需要在HTML的viedeo元素必须包含webkit-playsinline属性。默认iPhone为NO,iPad为YES。
@property (nonatomic) BOOL allowsInlineMediaPlayback NS_AVAILABLE_IOS(4_0); // iPhone Safari defaults to NO. iPad Safari defaults to YES

//这个属性决定了HTML5视频可以自动播放还是需要用户启动播放。iPhone和iPad默认都是YES。
@property (nonatomic) BOOL mediaPlaybackRequiresUserAction NS_AVAILABLE_IOS(4_0); // iPhone and iPad Safari both default to YES

//这个属性决定了从这个页面是否可以Air Play。iPhone和iPad上都是默认YES。
@property (nonatomic) BOOL mediaPlaybackAllowsAirPlay NS_AVAILABLE_IOS(5_0); // iPhone and iPad Safari both default to YES

//这个值决定了网页内容的渲染是否在把内容全部加到内存中再去处理。如果设置为YES,只有网页内容加载到内存里了才会去渲染。默认为NO
@property (nonatomic) BOOL suppressesIncrementalRendering NS_AVAILABLE_IOS(6_0); // iPhone and iPad Safari both default to NO

//这个属性如果设置为YES,用户必须明确的点击页面上的元素或者相关联的输入页面来显示键盘。如果设置为NO,一个元素的焦点事件就会导致输入视图的显示和自动关联这个元素。
@property (nonatomic) BOOL keyboardDisplayRequiresUserAction NS_AVAILABLE_IOS(6_0); // default is YES
//设置页面分页模型选择。
@property (nonatomic) UIWebPaginationMode paginationMode NS_AVAILABLE_IOS(7_0);
//这个属性决定了CSS属性是采用column-break 还是page-breaking样式。
@property (nonatomic) UIWebPaginationBreakingMode paginationBreakingMode NS_AVAILABLE_IOS(7_0);

//分页的长度
@property (nonatomic) CGFloat pageLength NS_AVAILABLE_IOS(7_0);
//分页之间间距
@property (nonatomic) CGFloat gapBetweenPages NS_AVAILABLE_IOS(7_0);
//分页的个数
@property (nonatomic, readonly) NSUInteger pageCount NS_AVAILABLE_IOS(7_0);

//是否允许画中画播放The default value is YES on devices that support Picture in Picture (PiP) mode and NO on all other devices.
@property (nonatomic) BOOL allowsPictureInPictureMediaPlayback NS_AVAILABLE_IOS(9_0);

//3DTouch的预览功能,默认为NO
@property (nonatomic) BOOL allowsLinkPreview NS_AVAILABLE_IOS(9_0); // default is NO
@end

__TVOS_PROHIBITED @protocol UIWebViewDelegate <NSObject>

@optional
- (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;

@end

NS_ASSUME_NONNULL_END

现在小编告诉你, 终于进行我们要讲的主题了,不好意思,现在才开始,不过也会很快结束的。

3 WKKit && WKWebview
3.1WKKit框架介绍,WKWebView API介绍

WKWebview在WKKit这个框架里, 苹果将UIWebViewDelegate和UIWebView重构成了14个类与3个协议官方链接

WebKit provides a set of classes to display web content in windows, and implements browser features such as following links when clicked by the user, managing a back-forward list, and managing a history of pages recently visited. WebKit greatly simplifies the complicated process of loading webpages—that is, asynchronously requesting web content from an HTTP server where the response may arrive incrementally, in random order, or partially due to network errors. WebKit also simplifies the process of displaying that content which can contain various MIME types, and compound frame elements each with their own set of scroll bars.

在Overview的时候,苹果官方文档有这样的一段描述。
并且还有一个提示

Concurrency Note
The WebKit framework is not thread-safe. If you call functions or methods in this framework, you must do so exclusively on the main program thread.

不是线程安全的。
同样的WebKit框架在MAC app和 iOS APP里是不一样的, 我们可以看到在macAPP里,这个框架是很大的, 里面还包含了子框架, 而且这个框架暴露的头文件也很多,有CSS,DOM等操作的API.

说了这么多,我们稍等一会就只来看看iOS上webkit框架,如果大家刚兴趣可以学习或者了解一下MAC上的webkit框架

WWDC2014-206session:
The modern Webkit APi
1.Same on iOS and OS X
2.Modern
3.Streamlined(精简的)
4.Multi-process architecture

Multi-process architecture

我们首先了解下WKWebView的一些优势:
1WKWebview在性能、稳定性上和UIwebview相比
2WKWebView更多的支持HTML5的特性
3WKWebView更快,占用内存可能只有UIWebView的1/3 ~ 1/4
4WKWebView高达60fps的滚动刷新率和丰富的内置手势(Built-in gestures)
5WKWebView具有Safari相同的JavaScript引擎Nitro(JJT四个进程解释执行优化js代码)(Fast JavaScript)
6WKWebView增加了加载进度属性
7Easy app-webpage communication
8Responsive scrolling
9更省电量 battery
以上信息可以在WWDC2014-206节-介绍 WebKit modern API 的时候提到。

**(lldb) po [self.webView  recursiveDescription]
<UIWebView: 0x14d14130; frame = (0 0; 320 504); layer = <CALayer: 0x12cca690>>
   | <_UIWebViewScrollView: 0x6c55400; frame = (0 0; 320 504); clipsToBounds = YES; autoresize = H; gestureRecognizers = <NSArray: 0x12c7adf0>; layer = <CALayer: 0x10a4f9c0>; contentOffset: {0, 0}; contentSize: {320, 504}>
   |    | <UIWebBrowserView: 0x69fac00; frame = (0 0; 320 504); text = ''; gestureRecognizers = <NSArray: 0x10ac37f0>; layer = <UIWebLayer: 0x12c9f580>>
   |    |    | <LegacyTileHostLayer: 0x14d04c30> (layer)
   |    |    |    | <LegacyTileLayer: 0x10a99700> (layer)
   |    | <UIImageView: 0x14d0ffd0; frame = (3 498.5; 314 2.5); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x14d100b0>>
   |    | <UIImageView: 0x14c4e3a0; frame = (314.5 3; 2.5 498); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x14ceb340>>
**(lldb) po [self.webView recursiveDescription]**
<WKWebView: 0x1612c8720; frame = (0 0; 320 504); layer = <CALayer: 0x16127b9b0>>
   | <WKScrollView: 0x160977600; baseClass = UIWebScrollView; frame = (0 0; 320 504); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x16488d250>; layer = <CALayer: 0x16488a970>; contentOffset: {0, 0}; contentSize: {0, 0}>
   |    | <WKContentView: 0x160a56000; frame = (0 0; 320 504); gestureRecognizers = <NSArray: 0x164887d20>; layer = <CALayer: 0x16127b650>>
   |    |    | <UIView: 0x16129d960; frame = (0 0; 320 504); clipsToBounds = YES; layer = <CALayer: 0x1612c3150>>
   |    |    |    | <UIView: 0x1612c2fe0; frame = (0 0; 320 504); autoresize = W+H; layer = <CALayer: 0x16128a9f0>>
   |    | <UIView: 0x164887750; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x16127a070>>
   |    | <UIImageView: 0x16102fa70; frame = (310 498.5; 7 2.5); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x161227960>>
   |    | <UIImageView: 0x163647e60; frame = (314.5 494; 2.5 7); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x1610ac060>>


UIWebView.png 引用自CocoaChina

UIWebView属于UIKit,封装了WebKit.framework的WebView.WebView组合管理了WebCore.framework的Page,并提供了各种Clients.Page管理了Main Frame,Main Frame管理了sub Frame(FrameTree)
我们看一下po出来的视图层级完全不一样,可以说两者关系并不大,
WebView继承自WAKView,WAKView类似于NSView,可以做较少的改动使得Mac和iOS共用一套。由UIWebDocumentView对WebView进行操作并接收回调事件,当数据发生变化的时候,就会通知UIWebTiledView重新绘制。
UIWebTiledView和WAKWindow这两个类主要负责页面的绘制,包括布局绘图排版,交互等,WAKWindow还会做一些用户操作事件的分派。UIWebBrowserView主要负责: form的自动填充 fixed元素的位置调整JavaScript的手势识别, 键盘弹出时的视图滚动处理,防止遮挡,提供接口让UIWebView获取信息,为显示PDF时添加页号标签 - -- 引在cocoachina一篇文章

但是我在实际测试的时候, 在真机上出现过收到内存警告。
下面两张图片对比下加载www.fang.com的wap页的时候,以及点击wap的时候内存变化的情况。

UIWebView内存变化情况
WKWebview内存变化情况

从上面一个图片可以看出,刚开始加载网页的时候内存使用是差不多的, 但是
当我和wap页面交互的时候,内存变化对于UIWebview来说是巨大的, 对于WkWebView来说相对是较小的。

Class:

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

WKUserScript:

What Can User Scripts Do?
Incredibly powerful
• Modify the document
• Listen for events
• Load resources
• Communicate back to your application

Protocal:

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

现在让我们跳到Xcode



/*
说明:
 1.WKProcessPool
@abstract The process pool from which to obtain the view's web content
 process.
 @discussion When a web view is initialized, a new web content process
 will be created for it from the specified pool, or an existing process in
 that pool will be used.
 ! A WKProcessPool object represents a pool of web content processes.
 The process pool associated with a web view is specified by its web view
 configuration. Each web view is given its own web content process until an
 implementation-defined process limit is reached; after that, web views
 with the same process pool end up sharing web content processes.
 这个东西好像与cookie有关,一个例子。

 2WKUserContentController
 A WKUserContentController object provides a way for JavaScript to post
 messages to a web view.
 The user content controller associated with a web view is specified by its
 web view configuration.
 
 3
 它是用于webview内容交互时选择内容的粒度类型设置。比如说,当使用WKSelectionGranularityDynamic时,而所选择的内容是单个块,这时候granularity可能会是单个字符;当所选择的web内容不限制于某个块时,granularity可能会是单个块。

*/

#import "LLLWKwebViewViewController.h"
//1.第一步导入框架
#import <WebKit/WebKit.h>
//大家可以看一下,这个Webkit包含了27个文件,我们不能讲的很详细, 只能尽力去说一下

@interface LLLWKwebViewViewController ()<WKNavigationDelegate,WKUIDelegate>

@property(nonatomic,strong)WKWebView *wkwebview;

@end

@implementation LLLWKwebViewViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
//    WKWebView *wkView = [[WKWebView alloc]initWithFrame:self.view.bounds];
    
    //3.初始化WKPreferences
    WKPreferences *preference = [[WKPreferences alloc]init];
    preference.minimumFontSize = 0;
    preference.javaScriptEnabled = YES;
    preference.javaScriptCanOpenWindowsAutomatically=NO;
    
    //4初始化WKUserContentController
    WKUserContentController *wkuserCVC = [[WKUserContentController alloc]init];
   // [wkuserCVC addUserScript:nil];
   // [wkuserCVC addScriptMessageHandler:<#(nonnull id<WKScriptMessageHandler>)#> name:<#(nonnull NSString *)#>]
    
    //5.WKWebsiteDataStore
    WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
    dataStore = [WKWebsiteDataStore nonPersistentDataStore];//适合隐私浏览
    NSSet *set = [WKWebsiteDataStore allWebsiteDataTypes];
    NSArray *arr = [set allObjects];
   // [dataStore fetchDataRecordsOfTypes:(nonnull NSSet<NSString *> *) completionHandler:^(NSArray<WKWebsiteDataRecord *> * _Nonnull) {
        
  //  }];
   // [dataStore removeDataOfTypes:(nonnull NSSet<NSString *> *) forDataRecords:(nonnull NSArray<WKWebsiteDataRecord *> *) completionHandler:^{
        
    //}];
    //6WKWebsiteDataRecord
   // WKWebsiteDataRecord *dataRecord = [[WKWebsiteDataRecord alloc]init];
   // dataRecord.dataTypes
    
    //2.初始化设置
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc]init];
    config.processPool = [[WKProcessPool alloc]init];
    config.preferences = preference;
    config.userContentController = wkuserCVC;
    config.websiteDataStore = dataStore;
    config.suppressesIncrementalRendering = NO;//ioS9
    config.applicationNameForUserAgent = @"USer-agent";//ios9
    config.allowsAirPlayForMediaPlayback =YES;//airplay isallowed
    config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeAll;//ios10
    config.allowsInlineMediaPlayback = NO;
    config.selectionGranularity = WKSelectionGranularityDynamic;
    config.allowsPictureInPictureMediaPlayback = NO;
    config.dataDetectorTypes = UIDataDetectorTypePhoneNumber;
    config.ignoresViewportScaleLimits = NO;
    
    WKWebView*wkView = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:config];
    self.wkwebview = wkView;
    WKBackForwardList *list = wkView.backForwardList;
    WKBackForwardListItem *backlistItem =  list.backItem;
    WKBackForwardListItem *forwardlistItem =  list.forwardItem;
    WKBackForwardListItem *currentlistItem = list.currentItem;
    WKBackForwardListItem *nextlistItem =  [list itemAtIndex:1];
    NSArray *backList =  list.backList;
    NSArray *forwardList =  list.forwardList;
    
    WKNavigation *navigation = [wkView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.fang.com"]]];
    wkView.UIDelegate = self;
    wkView.navigationDelegate = self;
    [self.view addSubview:wkView];
    

    
    
}

#pragma mark---WKNavigationDelegate

/*! @abstract Decides whether to allow or cancel a navigation.
 @param webView The web view invoking the delegate method.
 @param navigationAction Descriptive information about the action
 triggering the navigation request.
 @param decisionHandler The decision handler to call to allow or cancel the
 navigation. The argument is one of the constants of the enumerated type WKNavigationActionPolicy.
 @discussion If you do not implement this method, the web view will load the request or, if appropriate, forward it to another application.
 */

//1.The number 1 called
//3.The number 3 called
//7.The number 7 called

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{

    NSLog(@"%s--navigationAction is %@",__FUNCTION__,navigationAction);

    decisionHandler(WKNavigationActionPolicyAllow);

}

/*! @abstract Decides whether to allow or cancel a navigation after its
 response is known.
 @param webView The web view invoking the delegate method.
 @param navigationResponse Descriptive information about the navigation
 response.
 @param decisionHandler The decision handler to call to allow or cancel the
 navigation. The argument is one of the constants of the enumerated type WKNavigationResponsePolicy.
 @discussion If you do not implement this method, the web view will allow the response, if the web view can show it.
 */
//5.The number 5 called

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    
    NSLog(@"%s--navigationAction is %@",__FUNCTION__,navigationResponse);

    decisionHandler(WKNavigationResponsePolicyAllow);
    

}

/*! @abstract Invoked when a main frame navigation starts.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 #########Called when web content begins to load in a web view.
 */
//2.the number 2 called
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{

    NSLog(@"%s--navigation is %@",__FUNCTION__,navigation);

}

/*! @abstract Invoked when a server redirect is received for the main
 frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
//4.The number 4 called

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

    NSLog(@"%s--navigation is %@",__FUNCTION__,navigation);

}

/*! @abstract Invoked when an error occurs while starting to load data for
 the main frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 @param error The error that occurred.
 */
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{

    NSLog(@"%s--navigation is %@ and error is %@",__FUNCTION__,navigation,error);

}

/*! @abstract Invoked when content starts arriving for the main frame.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 ################Called when the web view begins to receive web content.

 */
//6.The number 6 called

- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation{

    NSLog(@"%s--navigation is %@",__FUNCTION__,navigation);

}

/*! @abstract Invoked when a main frame navigation completes.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 */
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation{

    NSLog(@"%s--navigation is %@",__FUNCTION__,navigation);

}

/*! @abstract Invoked when an error occurs during a committed main frame
 navigation.
 @param webView The web view invoking the delegate method.
 @param navigation The navigation.
 @param error The error that occurred.
 */
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error{

    NSLog(@"%s--navigation is %@",__FUNCTION__,navigation);

}

/*! @abstract Invoked when the web view needs to respond to an authentication challenge.
 @param webView The web view that received the authentication challenge.
 @param challenge The authentication challenge.
 @param completionHandler The completion handler you must invoke to respond to the challenge. The
 disposition argument is one of the constants of the enumerated type
 NSURLSessionAuthChallengeDisposition. When disposition is NSURLSessionAuthChallengeUseCredential,
 the credential argument is the credential to use, or nil to indicate continuing without a
 credential.
 @discussion If you do not implement this method, the web view will respond to the authentication challenge with the NSURLSessionAuthChallengeRejectProtectionSpace disposition.
 */
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{

    NSLog(@"%s--challenge is %@",__FUNCTION__,challenge);

    completionHandler(NSURLSessionAuthChallengeUseCredential,nil);
}

/*! @abstract Invoked when the web view's web content process is terminated.
 @param webView The web view whose underlying web content process was terminated.
 */
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0)){
    NSLog(@"%s-",__FUNCTION__);

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

#pragma mark---WKUIDelegate
/*! @abstract Creates a new web view.
 @param webView The web view invoking the delegate method.
 @param configuration The configuration to use when creating the new web
 view.
 @param navigationAction The navigation action causing the new web view to
 be created.
 @param windowFeatures Window features requested by the webpage.
 @result A new web view or nil.
 @discussion The web view returned must be created with the specified configuration. WebKit will load the request in the returned web view.
 
 If you do not implement this method, the web view will cancel the navigation.
 */
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{

    NSLog(@"%s--configuration is %@ \n and navigationAction is %@\n and windowFeatures is %@",__FUNCTION__,configuration,navigationAction,windowFeatures);
    return webView;
}

/*! @abstract Notifies your app that the DOM window object's close() method completed successfully.
 @param webView The web view invoking the delegate method.
 @discussion Your app should remove the web view from the view hierarchy and update
 the UI as needed, such as by closing the containing browser tab or window.
 */
- (void)webViewDidClose:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0)){

    NSLog(@"%s-",__FUNCTION__);

}

/*! @abstract Displays a JavaScript alert panel.
 @param webView The web view invoking the delegate method.
 @param message The message to display.
 @param frame Information about the frame whose JavaScript initiated this
 call.
 @param completionHandler The completion handler to call after the alert
 panel has been dismissed.
 @discussion For user security, your app should call attention to the fact
 that a specific website controls the content in this panel. A simple forumla
 for identifying the controlling website is frame.request.URL.host.
 The panel should have a single OK button.
 
 If you do not implement this method, the web view will behave as if the user selected the OK button.
 */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{

    NSLog(@"%s and message is %@ and frame is %@",__FUNCTION__,message,frame);

}

/*! @abstract Displays a JavaScript confirm panel.
 @param webView The web view invoking the delegate method.
 @param message The message to display.
 @param frame Information about the frame whose JavaScript initiated this call.
 @param completionHandler The completion handler to call after the confirm
 panel has been dismissed. Pass YES if the user chose OK, NO if the user
 chose Cancel.
 @discussion For user security, your app should call attention to the fact
 that a specific website controls the content in this panel. A simple forumla
 for identifying the controlling website is frame.request.URL.host.
 The panel should have two buttons, such as OK and Cancel.
 
 If you do not implement this method, the web view will behave as if the user selected the Cancel button.
 */
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler{

    NSLog(@"%s and message is %@ and frame is %@",__FUNCTION__,message,frame);

}

/*! @abstract Displays a JavaScript text input panel.
 @param webView The web view invoking the delegate method.
 @param message The message to display.
 @param defaultText The initial text to display in the text entry field.
 @param frame Information about the frame whose JavaScript initiated this call.
 @param completionHandler The completion handler to call after the text
 input panel has been dismissed. Pass the entered text if the user chose
 OK, otherwise nil.
 @discussion For user security, your app should call attention to the fact
 that a specific website controls the content in this panel. A simple forumla
 for identifying the controlling website is frame.request.URL.host.
 The panel should have two buttons, such as OK and Cancel, and a field in
 which to enter text.
 
 If you do not implement this method, the web view will behave as if the user selected the Cancel button.
 */
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{

    NSLog(@"%s and prompt is %@ and defaulttext is %@ and frame is %@",__FUNCTION__,prompt,defaultText,frame);

}

/*! @abstract Allows your app to determine whether or not the given element should show a preview.
 @param webView The web view invoking the delegate method.
 @param elementInfo The elementInfo for the element the user has started touching.
 @discussion To disable previews entirely for the given element, return NO. Returning NO will prevent
 webView:previewingViewControllerForElement:defaultActions: and webView:commitPreviewingViewController:
 from being invoked.
 
 This method will only be invoked for elements that have default preview in WebKit, which is
 limited to links. In the future, it could be invoked for additional elements.
 */
- (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo API_AVAILABLE(ios(10.0)){

    NSLog(@"%s-%@",__FUNCTION__, elementInfo);
    return YES;

}

/*! @abstract Allows your app to provide a custom view controller to show when the given element is peeked.
 @param webView The web view invoking the delegate method.
 @param elementInfo The elementInfo for the element the user is peeking.
 @param defaultActions An array of the actions that WebKit would use as previewActionItems for this element by
 default. These actions would be used if allowsLinkPreview is YES but these delegate methods have not been
 implemented, or if this delegate method returns nil.
 @discussion Returning a view controller will result in that view controller being displayed as a peek preview.
 To use the defaultActions, your app is responsible for returning whichever of those actions it wants in your
 view controller's implementation of -previewActionItems.
 
 Returning nil will result in WebKit's default preview behavior. webView:commitPreviewingViewController: will only be invoked
 if a non-nil view controller was returned.
 */
- (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray<id <WKPreviewActionItem>> *)previewActions API_AVAILABLE(ios(10.0)){

    NSLog(@"%s",__FUNCTION__);
    return nil;
}

/*! @abstract Allows your app to pop to the view controller it created.
 @param webView The web view invoking the delegate method.
 @param previewingViewController The view controller that is being popped.
 */
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController API_AVAILABLE(ios(10.0)){

    NSLog(@"%s",__FUNCTION__);

}
@end

3.2 OC如何给JS注入对象及JS如何给IOS发送数据

1.通过直接注入js代码, 我们可以做很多事情,上面已经提到
1 .1Modify the document1.2 Listen for events1.3 Load resources1.4Communicate back to your application

    WKUserContentController *wkuserCVC = [[WKUserContentController alloc]init];
    WKUserScript *script = [[WKUserScript alloc]initWithSource:@"js code" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
    [wkuserCVC addUserScript:script];

2.WKUserContentController是用于给JS注入对象的,注入对象后,JS端就可以使用:

window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 

来调用发送数据给iOS端,比如:

window.webkit.messageHandlers.AppModel.postMessage({body: '传数据'});

OC则是注入对象是,设置了一个代理

#pragma mark @protocol WKScriptMessageHandler <NSObject>

/*! @abstract Invoked when a script message is received from a webpage.
 @param userContentController The user content controller invoking the
 delegate method.
 @param message The script message received.
 */
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{

    NSLog(@"%s and messgae is %@\n\n\n",__FUNCTION__,message);

}

现在我们通过上述代码的学习,基本大体了解了WKwebview 的API的使用,
这里面大约总共有27多个类, 还有很多的协议。常用的我们首先掌握了, 然后
不常用的大家也可以多研究一下。

3.3 JS调用alert、confirm、prompt时,不采用JS原生提示,而是使用iOS原生来实现

OC代码咋是收到代理以后,展示原生的控件,代码如下,这里我们

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler{

    NSLog(@"%s\n\n\n prompt:%@  defaultText:%@ frame:%@",__FUNCTION__,prompt,defaultText,frame);
    
    UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:@"prompt" message:[NSString stringWithFormat:@"prompt:%@\ndefaultText:%@\nframe:%@",prompt,defaultText,frame] preferredStyle:UIAlertControllerStyleAlert];
    [alertVc addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.textColor = [UIColor blueColor];
    }];
    [alertVc addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler([[alertVc.textFields lastObject] text]);
    }]];
    [self presentViewController:alertVc animated:YES completion:^{
        
    }];

}

3.4 如何监听web内容加载进度、是否加载完成

WKWebView 有一个属性estimatedProgress,这个就是加载进度。我们利用KVO监听这个属性值的变化,就可以显示加载进度了。

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

3.5如何处理去跨域问题

#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
  NSString *hostname = navigationAction.request.URL.host.lowercaseString;
  if (navigationAction.navigationType == WKNavigationTypeLinkActivated
      && ![hostname containsString:@".baidu.com"]) {
// 对于跨域,需要手动跳转
    [[UIApplication sharedApplication] openURL:navigationAction.request.URL];
    
    // 不允许web内跳转
    decisionHandler(WKNavigationActionPolicyCancel);
  } else {
    self.progressView.alpha = 1.0;
    decisionHandler(WKNavigationActionPolicyAllow);
  }
  
    NSLog(@"%s", __FUNCTION__);
}

3.6 WKWebView关于缓存

因为现在WKWebView会忽视默认的网络存储, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。 目前是这样的,WKWebView有自己的进程,同样也有自己的存储空间用来存储cookie和cache, 其他的网络类如NSURLConnection是无法访问到的。 同时WKWebView发起的资源请求也是不经过NSURLProtocol的,导致无法自定义请求
不可否认的,WKWebView确实要好用,APP内的表现也很好,但吐槽的地方也是蛮多的。

    WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
//    dataStore = [WKWebsiteDataStore nonPersistentDataStore];//适合隐私浏览
    NSSet *set = [WKWebsiteDataStore allWebsiteDataTypes];
    NSArray *arr = [set allObjects];
   // [dataStore fetchDataRecordsOfTypes:(nonnull NSSet<NSString *> *) completionHandler:^(NSArray<WKWebsiteDataRecord *> * _Nonnull) {
        
  //  }];
    [dataStore removeDataOfTypes:set forDataRecords:arr completionHandler:^{
        
    }];
    [dataStore removeDataOfTypes:set modifiedSince:[NSDate dateWithTimeIntervalSinceNow:-100] completionHandler:^{
        
    }];
    

3.7JS和OC的相互调用

第一种:有很多的app直接使用在webview的代理中通过拦截的方式与native进行交互,通常是通过拦截url scheme判断是否是我们需要拦截处理的url及其所对应的要处理的功能是什么。任意版本都支持。
第二种:iOS7之后出了JavaScriptCore.framework用于与JS交互,但是不支持iOS6,对于还需要支持iOS6的app,就不能考虑这个了。若需要了解,看最后的推荐阅读。
第三种:WebViewJavascriptBridge开源库使用,本质上,它也是通过webview的代理拦截scheme,然后注入相应的JS。
第四种:react-native

对于第一种,其实在上文的讲解中可能穿插了, 这里简单总结下:
1.OC调用JS代码

UIWebview:
[webView stringByEvaluatingJavaScriptFromString:@"helloWithName('jack')"];
WKWebview:
 [self.webView evaluateJavaScript:@"document.getElementById('soufunclient').innerHTML" completionHandler:^(id _Nullable string, NSError * _Nullable error) {
        shareContent = [NSString stringWithFormat:@"%@",string];
  }];
//在这里说明一下,上面的方法是同步的,而WKwebView的方法是异步的。

2JS调用OC代码
UIWebView:

//具体让js通知native进行方法调用,我们可以让js产生一个特殊的请求。可以让Native代码可以拦截到,而且不然用户察觉。业界一般的实现方案是在网页中加载一个隐藏的iframe来实现该功能。通过将iframe的src指定为一个特殊的URL,实现在- (BOOL)webView:(UIWebView )webView shouldStartLoadWithRequest:(NSURLRequest )request navigationType:(UIWebViewNavigationType)navigationType;方案中进行拦截处理。对应的js调用代码如下:



 function loadURL(url) {
        var iFrame;
        iFrame = document.createElement("iframe");
        iFrame.setAttribute("src", url);
        iFrame.setAttribute("style", "display:none;");
        iFrame.setAttribute("height", "0px");
        iFrame.setAttribute("width", "0px");
        iFrame.setAttribute("frameborder", "0");
        document.body.appendChild(iFrame);
        // 发起请求后这个iFrame就没用了,所以把它从dom上移除掉
        iFrame.parentNode.removeChild(iFrame);
        iFrame = null;
    }

WKWebView:
对于WkwebView我们上面3.2 OC如何给JS注入对象及JS如何给IOS发送数据
已经讲到了。

当然这里的总结是最简单的。
大家有没有想过,我们怎么才能知道js里的哪些方法OC可以调用,同样OC里哪些模块,JS也知道可以调用呢,React Native 是FaceBook开源的, 大家可以自行百度学习,很牛啊。

对于第二种:javascriptcore.framework
我们之前分享JSPatch的时候简单讲过,这里我们再从这篇文章简单讲一下js和OC是通过这个框架相互调用的方法。
http://nshipster.com/javascriptcore/
不知道大家最近有没有关注微信小程序,微信小程序的代码就是允许在微信宿主程序的JavaScriptcore环境中的。大家可以下载微信官方给的开发工具进行开发微信小程序,具体学习参考微信的官方文档说明。

对于第三种WebViewJavascriptBridge,第三方框架,挺出名的, 具体的实现是通过webview拦截的方式。我没有细致的去看,就不讲了,感兴趣的同学可以研究。
http://www.cocoachina.com/ios/20150811/12985.html

对于React Native ,这个实现原理很牛掰,大家自己可以搜一下原理看看,这个采用JSX编写,就js和html……,这个东西没有学过,我也不多说了,有时间还是需要学习一下的,有些公司早就应用生产了,解决了想热修复一样的功能。

现在JS和OC交互我们大体就讲完了。
1UIWebview拦截 ,执行js代码
2WkWebview注入js对象,OC回调,当然我个人认为在服务器发送响应头也是可以做拦截的,和UIwebview一样,不过没有发现网上有这样说的。
3就是WebViewJavascriptBridge第三方拦截框架,根本上是对上面的封装
4ReacNative 这个就不说了,基本就是用的 JSBridge ,运行时

3.8WKWebview API问题

最后我们来学习这两篇文章
1.https://github.com/ShingoFukuyama/WKWebViewTips/blob/master/README.md
2.http://nshipster.cn/wkwebkit/
3.http://blog.csdn.net/j_av_a/article/details/52160413
这篇文章是WkWebview的一些坑, 文章是一个日本人整理的,是一篇非常不错的文章。

进阶

1.使用safari对webview进行调试

http://www.brighttj.com/ios/ios-user-safari-debug-webview.html
当然这是对于web开发的, 感兴趣的同学也可以学习一下

2.WebView加载HTML图片大小自适应与文章自动换行

http://www.brighttj.com/ios/ios-webview-load-html-image-adaptive.html
这是CSS一个简单小例子,有的同学大学都学过css,html5,javascript
大家感兴趣可以学习js的DOM 编程,Ajax,jquery框架,当然如果你对前端感兴趣可以修行一下

3.WKWebViewでJavaScript(Alert,Confirm,Prompt)の処理

http://qiita.com/ShingoFukuyama/items/5d97e6c62e3813d1ae98

4WBWebViewConsole

https://github.com/Naituw/WBWebViewConsole

5 iOS10ATS问题

https://onevcat.com/2016/06/ios-10-ats/

6Cookie问题http://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容

  • 前言 关于UIWebView的介绍,相信看过上文的小伙伴们,已经大概清楚了吧,如果有问题,欢迎提问。 本文是本系列...
    CoderLF阅读 8,844评论 2 12
  • 虽然WKWebView是在Apple的WWDC 2014随iOS 8和OS X 10.10出来的,是为了解决UIW...
    winann阅读 134,000评论 196 642
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,551评论 4 58
  • 我们本是敌对,却偏要固执的在一起,也许,天堂,是我们最好的归宿。 01. 大火在不远处以势不可挡的姿态向我们袭来,...
    龟小慢阅读 821评论 4 3
  • 博文《像墨一样的阳光》中有一段话读起来总觉得别扭,但又说不上来哪里不对,原文如下: “也许,导演三池崇史一开始就用...
    关中人阅读 363评论 0 1