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
@property (nullable, nonatomic, assign) id 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
@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 这个框架里,WKWebView
较UIWebView
有哪些优势呢?
-
WKWebview
在性能、稳定性上和UIwebview相比提升了非常多 -
WKWebView
更多的支持HTML5的特性 -
WKWebView
更快,占用内存可能只有UIWebView的1/3 ~ 1/4 -
WKWebView
高达60fps的滚动刷新率和丰富的内置手势(Built-in gestures) -
WKWebView
具有Safari相同的JavaScript引擎Nitro(JJT四个进程解释执行优化js代码)(Fast JavaScript) -
WKWebView
增加了加载进度属性 - Easy app-webpage communication
- Responsive scrolling
- 更省电量 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
根据不同的协议进行不同的处理,这样的方式适合UIWebView
及WKWebView
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原生的提示
WKWebview
的UIDelegate
提供了几个 方法,供我们实现我们自己样式的
// 针对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的坑
加载本地的 html , 在iOS9.0之前 file:/// 不能识别,得将本地加载到一个 临时文件中加载, iOS9.0之后 新增了一个
loadFileUrl
的方法不能打开一个 新的页面, 即 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;
}
- 相关的 Scheme 和 AppStore links 都无法直接跳转
要在
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
}
里面 进行判断,通过
[[UIApplication sharedApplication] openURL:url];
去跳转
不能 用
NSURLProtocal
截获网络了 , 该类协议方法都失效了网页不再获取默认的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"]; (注意是大写)
- 内存泄露
在注入脚本的时候 调用addScriptMessageHandler
以后 ,在webview销毁之前 一定要removeMessageHandler
,否则会内存泄露
好了, 以上就是我在使用WKWebView遇到的问题,如果大家以后有使用WKWebview的需求, 希望该文会对大家有所帮助。。。