UIWebView & WKWebView 以及 WKWebView的坑

1. 移动端展示Web方式

目前,iOS端展示Web的方式大致可分为四种:

  • safari 展示
  • iOS9提供的SFSafariViewController展示
  • UIWebview
  • WKWebview

第一种方式是我们去调用openUrl: 通过系统的浏览器展示我们的web页面,这是苹果提供给我们最原始的方法。

NSURL *URL = [NSURL urlWithString:@"http:[www.baidu.com"]];
[[UIApplication sharedApplication] openUrl:URL];

第二种是iOS9提供了一个继承于UIViewController的浏览器,使用方式很简单,直接initWithUrl: 然后present或者push 就可以访问web页, 查看它相关的api,实在是太少了, 从而无法自定义,这个相当于系统的safari。

第三种就是iOS7.0之前大家都在用的UIWebView,它基于UIKit框架,继承UIView,整个头文件寥寥无几,不超过100行。下面介绍UIWebView的使用

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

第四种就是WKWebView,也是本文的重点,它诞生于iOS8以后,介于我们现在的app最低版本已支持到iOS8了,有这么好的东西,何不尝试一下呢?
WKWebView 在 WKKit 这个框架里,WKWebViewUIWebView有哪些优势呢?

  1. WKWebview在性能、稳定性上和UIwebview相比提升了非常多
  2. WKWebView更多的支持HTML5的特性
  3. WKWebView更快,占用内存可能只有UIWebView的1/3 ~ 1/4
  4. WKWebView高达60fps的滚动刷新率和丰富的内置手势(Built-in gestures)
  5. WKWebView具有Safari相同的JavaScript引擎Nitro(JJT四个进程解释执行优化js代码)(Fast JavaScript)
  6. WKWebView增加了加载进度属性
  7. Easy app-webpage communication
  8. Responsive scrolling
  9. 更省电量 battery

以上信息可以在WWDC2014-206节-介绍 WebKit modern API 的时候提到。

WKWebView 头文件 属性介绍

WKBackForwardList: 之前访问过的 web 页面的列表,可以通过后退和前进动作来访问到。

WKBackForwardListItem: webview 中后退列表里的某一个网页。

WKFrameInfo: 包含一个网页的布局信息。

WKNavigation: 包含一个网页的加载进度信息。

WKNavigationAction: 包含可能让网页导航变化的信息,用于判断是否做出导航变化。

WKNavigationResponse: 包含可能让网页导航变化的返回内容信息,用于判断是否做出导航变化。

WKPreferences: 概括一个 webview 的偏好设置。

WKProcessPool: 表示一个 web 内容加载池。

WKUserContentController: 提供使用 JavaScript post 信息和注射 script 的方法。

WKScriptMessage: 包含网页发出的信息。

WKUserScript: 表示可以被网页接受的用户脚本。

WKWebViewConfiguration: 初始化 webview 的设置。

WKWindowFeatures: 指定加载新网页时的窗口属性。

protocal:

WKNavigationDelegate: 提供了追踪主窗口网页加载过程和判断主窗口和子窗口是否
进行页面加载新页面的相关方法。

WKScriptMessageHandler: 提供从网页中收消息的回调方法。

WKUIDelegate: 提供用原生控件显示网页的方法回调。

以上为WKWebView 头文件及协议方法

2. web进阶

OC如何给JS注入对象及JS如何给iOS发送消息

WKWebView没出来之前,在iOS7,系统提供了JaveScriptCore 这个获取Context,然后注入JS函数,JS 调用OC的方法
在iOS7,之前我们只能通过曲线救国的方式,让JS去调用OC的方法, 我们定义自己的协议scheme,比如
ubaby:// aa/aaaa
根据不同的协议进行不同的处理,这样的方式适合UIWebViewWKWebView

WKWebView提供一套JS 和 OC 交互的方法
WKUserContentController通过该类,用户可以注入JS 方法 。

js端 :{
    function openImage {
        window.webkit.messageHandlers.openImage.possMessage('');
    }
}

OC 端

WKUserContentController *userController = [[WKUserContentController alloc] init];
 [userController addScriptMessageHandler:self name:@"openImage"];

handler需要实现
WKScriptMessageHandler协议里的 


- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
}

这样就实现了 js --- > oc的交互
WKWebview 给我们提供里一个evaluateJavaScript:该方法 可以让OC 调用 JS 里面的方法
同样我们可以 自己注入一个JS 函数,然后 通过evaluateJavaScript调用

NSString *jsGetImages = @"    
 function openImage {
         
    }
";

WKUserScript *script = [[WKUserScript alloc] initWithSource:jsGetImages  injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];

[userController addUserScript:script];

[webView evaluateJavaScript:@"openImage"  completionHandler:^(id result ,NSError *error) {
    NSLog(@"%@",result);
}];

JS调用 alert, confirm , prompt 不在采用JS原生的提示

WKWebviewUIDelegate提供了几个 方法,供我们实现我们自己样式的

//  针对alert 
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
// 针对 confirm
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
// 针对 prompt
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;

可以通过以上方法自定我们自己的弹框

WKWebView的 KVO

WKWebView很多属性都可以被监听,然后我们进一步对其处理,比如:estimatedProgress, 这个属性是加载进度, 通过监听它的变化,我们可以知道网页的加载进度。

WKWebView的坑

  1. 加载本地的 html , 在iOS9.0之前 file:/// 不能识别,得将本地加载到一个 临时文件中加载, iOS9.0之后 新增了一个loadFileUrl的方法

  2. 不能打开一个 新的页面, 即 target = "_blank", 我们需要在协议方法里面判断

- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    
WKFrameInfo *frameInfo = navigationAction.targetFrame;
    if (![frameInfo isMainFrame]) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}
  1. 相关的 Scheme 和 AppStore links 都无法直接跳转

要在

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

}

里面 进行判断,通过
[[UIApplication sharedApplication] openURL:url];
去跳转

  1. 不能 用NSURLProtocal截获网络了 , 该类协议方法都失效了

  2. 网页不再获取默认的cookie, 如果后端有该方面的需求,我们就得自己传递cookie给后端

//1. js 注入 cookie
    NSMutableDictionary *cookiesDictionary = [NSMutableDictionary dictionary];
    NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies;
    
    for (NSHTTPCookie *cookie in cookies) {
        [cookiesDictionary setValue:cookie.value forKey:cookie.name];
    }
    
    NSMutableArray *cookieArray = [NSMutableArray array];
    for (NSString *key in cookiesDictionary) {
        [cookieArray addObject:[NSString stringWithFormat:@"%@=%@",key,cookiesDictionary[key]]];
    }
    
    if (cookieArray.count > 0) {
        WKUserScript *userScript = [[WKUserScript alloc] initWithSource:[NSString stringWithFormat:@"document.cookie='%@'",[cookieArray componentsJoinedByString:@"&"]] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
        [userContentController addUserScript:userScript];
    }
 

// 2. 还可以通过 requestHeader 传递该cookie

     NSString *cookie =  @"cookieKey1=cookieValue1;cookieKey2=cookieValue2";```

    [mutableRequest addValue:cookie forHTTPHeaderField:@"Cookie"]; (注意是大写)
  1. 内存泄露
    在注入脚本的时候 调用addScriptMessageHandler 以后 ,在webview销毁之前 一定要removeMessageHandler,否则会内存泄露

好了, 以上就是我在使用WKWebView遇到的问题,如果大家以后有使用WKWebview的需求, 希望该文会对大家有所帮助。。。

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

推荐阅读更多精彩内容