8.0之前都是UIWebView,以后苹果官方鼓励用WKWebView,本人建议也是用WKWebView,因为除了功能更加强大,性能和内存都会好很多,网上也有兼容两者的三方比如IMYWebView等。
1.UIWebView:
@interface QXWebViewController ()
@property (weak, nonatomic) IBOutlet UILabel *lable;
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property(nonatomic,assign)BOOL isToRequest;
@end
@implementation QXWebViewController
/**
// 代理属性 重点需要知道代理方法的使用
@property (nullable, nonatomic, assign) id delegate;
// 这个是webView内部的scrollView 只读,但是利用这个属性,设置scrollView的代理,就可以控制整个webView的滚动事件
@property(nonatomic, readonly, strong) UIScrollView *scrollView;
// webView的请求,这个属性一般在整个加载完成后才能拿到
@property (nullable, nonatomic, readonly, strong) NSURLRequest *request;
// A Boolean value indicating whether the receiver can move backward. (read-only)
// If YES, able to move backward; otherwise, NO.
// 如果这个属性为YES,才能后退
@property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack;
// A Boolean value indicating whether the receiver can move forward. (read-only)
// If YES, able to move forward; otherwise, NO.
// 如果这个属性为YES,才能前进
@property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward;
// A Boolean value indicating whether the receiver is done loading content. (read-only)
// If YES, the receiver is still loading content; otherwise, NO.
// 这个属性很好用,如果为YES证明webView还在加载数据,所有数据加载完毕后,webView就会为No
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
//A Boolean value determining whether the webpage scales to fit the view and the user can change the scale.
//If YES, the webpage is scaled to fit and the user can zoom in and zoom out. If NO, user zooming is disabled. The default value is NO.
// YES代表网页可以缩放,NO代表不可以缩放
@property (nonatomic) BOOL scalesPageToFit;
// 设置某些数据变为链接形式,这个枚举可以设置如电话号,地址,邮箱等转化为链接
@property (nonatomic) UIDataDetectorTypes dataDetectorTypes NS_AVAILABLE_IOS(3_0);
// iPhone Safari defaults to NO. iPad Safari defaults to YES
// 设置是否使用内联播放器播放视频
@property (nonatomic) BOOL allowsInlineMediaPlayback NS_AVAILABLE_IOS(4_0);
// iPhone and iPad Safari both default to YES
// 设置视频是否自动播放
@property (nonatomic) BOOL mediaPlaybackRequiresUserAction NS_AVAILABLE_IOS(4_0);
// iPhone and iPad Safari both default to YES
// 设置音频播放是否支持ari play功能
@property (nonatomic) BOOL mediaPlaybackAllowsAirPlay NS_AVAILABLE_IOS(5_0);
// iPhone and iPad Safari both default to NO
// 设置是否将数据加载入内存后渲染界面
@property (nonatomic) BOOL suppressesIncrementalRendering NS_AVAILABLE_IOS(6_0);
// default is YES
// 设置用户是否能打开keyboard交互
@property (nonatomic) BOOL keyboardDisplayRequiresUserAction NS_AVAILABLE_IOS(6_0);
以后的新特性
// 这个属性用来设置一种模式,当网页的大小超出view时,将网页以翻页的效果展示,枚举如下:
@property (nonatomic) UIWebPaginationMode paginationMode NS_AVAILABLE_IOS(7_0);
typedef NS_ENUM(NSInteger, UIWebPaginationMode) {
UIWebPaginationModeUnpaginated, //不使用翻页效果
UIWebPaginationModeLeftToRight, //将网页超出部分分页,从左向右进行翻页
UIWebPaginationModeTopToBottom, //将网页超出部分分页,从上向下进行翻页
UIWebPaginationModeBottomToTop, //将网页超出部分分页,从下向上进行翻页
UIWebPaginationModeRightToLeft //将网页超出部分分页,从右向左进行翻页
};
// This property determines whether certain CSS properties regarding column- and page-breaking are honored or ignored.
// 这个属性决定CSS的属性分页是可用还是忽略。默认是UIWebPaginationBreakingModePage
@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);
*/
(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view._webView.dataDetectorTypes=UIDataDetectorTypeAll;//监测所有数据
_webView.delegate=self;
[self oc2Js];[self loadPDF];
}
-(void)loadhttpStr{
NSString *strUrl=@"<html><head><title>Test WebView</title></head><body><h1>hello webview</h1><ul><li>1</li><li>2</li><li>3</li></body></html>";
[_webView loadHTMLString:strUrl baseURL:nil];
}
-(void)loadWWW{
NSURL *url=[NSURL URLWithString:@"https://www.hao123.com/"];
NSURLRequest *request=[NSURLRequest requestWithURL:url];
[_webView loadRequest:request];
}
//获取指定URL mimeType
-(NSString*)getMimeTypeOfUrl:(NSURL*)url{
NSURLRequest *request=[NSURLRequest requestWithURL:url];
//使用同步方法获取mimeType
NSHTTPURLResponse *reponse=nil;
NSError *error=nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&reponse error:&error];
if(!error){
return reponse.MIMEType;
}else{
return nil;
}
}
-(void)loadTxt{//类似的还可以加载本地html
NSString *path=[[NSBundle mainBundle] pathForResource:@”out” ofType:@”txt”];
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]];
[_webView loadRequest:request];
}
-(void)loadPDF{//加载本地PDF
NSString* path=[[NSBundle mainBundle] pathForResource:@"Groovy" ofType:@"pdf"];
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]];
[_webView loadRequest:request];
}
-(void)goBack{
[_webView goBack];
}
-(void)goForward{
[_webView goForward];
}
-(void)stopLoading{
[_webView stopLoading];
}
-(void)refreshing{
[_webView reload];
}
-(BOOL)canGoBack{
return [_webView canGoBack];
}
-(BOOL)canGoForward{
return [_webView canGoForward];
}
/**
与js交互
主要有两方面:js执行OC代码、oc调取写好的js代码
js执行OC代码:js是不能执行oc代码的,但是可以变相的执行,js可以将要执行的操作封装到网络请求里面,然后oc拦截这个请求,获取url里面的字符串解析即可,这里用到代理协议的- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType函数。
oc调取写好的js代码:这里用到UIwebview的一个方法。示例代码一个是网页定位,一个是获取网页title:
*/
-(void)oc2Js{
NSString *title=[_webView stringByEvaluatingJavaScriptFromString:@”document.title”];
NSLog(@"当前页面title=%@",title);
NSString *url=[_webView stringByEvaluatingJavaScriptFromString:@"document.location.href"];
NSLog(@"当前页面的URL=%@",url);
//修改利用js的知识修改html的属性
NSString *jsStr1=@"document.getElementByTagName('body')[0].style.webkitTextSizeAdjust='60%'";
[_webView stringByEvaluatingJavaScriptFromString:jsStr1];
//创建一个元素加入到html中
NSString *jsStr2=[NSString stringWithFormat:@"var meta=document.createElement(meta);meta.name='viewport';meta.content= 'width=%f,initial-scale=%f,minimum-scale=0.1,maximum-scale=1.0,user-scalable=yes'",_webView.frame.size.width,_webView.frame.size.width/1000];
[_webView setScalesPageToFit:YES];
[_webView stringByEvaluatingJavaScriptFromString:jsStr2];
// 实现自动定位js代码, htmlLocationID为定位的位置(由js开发人员给出),实现自动定位代码,应该在网页加载完成之后再调用
// NSString *javascriptStr = [NSString stringWithFormat:@”window.location.href = ‘#%@’”,htmlLocationID];
// // webview执行代码
// [self.webView stringByEvaluatingJavaScriptFromString:javascriptStr];
}
//是否对这个请求进行过滤处理 开始请求之前
-(BOOL)webView:(UIWebView )webView shouldStartLoadWithRequest:(NSURLRequest )request navigationType:(UIWebViewNavigationType)navigationType{
//比如对特殊的scheme或者url进行过滤 或者是解析js构成的商量好的协议,然后解析出来调用相应的OC代码,然后再处理 return YES;
NSString *urlStr=[[request URL] absoluteString];
urlStr=[urlStr stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSArray<NSString*> *compentArray=[urlStr componentsSeparatedByString:@"://"];
NSURL *url=[request URL];
NSString *scheme= url.scheme;
NSString *host=url.host;
NSString *path=url.path;
NSString *pathExtention=url.pathExtension;
NSString *query=url.query;
//上面的东西都有了你是想过滤什么就过滤什么啊 比如
if([host isEqualToString:@"main"]){
//跳转到mainViewController
}
if([pathExtention isEqualToString:@"goBack"]){
[webView goBack];
}
// typedef NS_ENUM(NSInteger, UIWebViewNavigationType) {
// UIWebViewNavigationTypeLinkClicked,
// UIWebViewNavigationTypeFormSubmitted,
// UIWebViewNavigationTypeBackForward,
// UIWebViewNavigationTypeReload,
// UIWebViewNavigationTypeFormResubmitted,
// UIWebViewNavigationTypeOther
// } __TVOS_PROHIBITED;
switch (navigationType) {
//进来的前的触发行为 然后针对性的过滤处理
case UIWebViewNavigationTypeLinkClicked:
//点击的是链接
break;
case UIWebViewNavigationTypeReload:
//reload 可以做reload记录数加1
break;
case UIWebViewNavigationTypeBackForward:
//点击的是向前
break;
case UIWebViewNavigationTypeFormSubmitted:
//提交表单
break;
case UIWebViewNavigationTypeFormResubmitted:
//重新提交表达
break;
default://UIWebViewNavigationTypeOther
break;
}
//也可以预先处理403和404状态码
NSHTTPURLResponse *response=nil;
NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
if(!_isToRequest){//代表这个请求是第一次或者是以前给isToRequest赋值过NO
if(response.statusCode==403){
//处理403
}else if(response.statusCode==404){
//处理404
}else{
[webView loadData:data MIMEType:@"text/html" textEncodingName:@"NSUTF8StringEncoding" baseURL:nil];
_isToRequest=YES;//已经经历过403和404的过滤 这次直接去请求吧 再走一次本方法
return NO;//代表本次请求到这里结束下面系统方法不走了 我拦截了
}
}else{
_isToRequest=NO;
return YES;
}
return YES;//我是拦截了修改了 些东西 但是本次请求还是照样走
}
-(void)webViewDidStartLoad:(UIWebView *)webView{//已经开始请求
NSURLRequest *request=[webView request];
NSString *requestMthod=[request HTTPMethod];
NSData *bodyData= [request HTTPBody];
BOOL b=[request HTTPShouldHandleCookies];
}
//请求完毕 服务端响应回调 没有错误
-(void)webViewDidFinishLoad:(UIWebView *)webView{
NSURLRequest *request=[webView request];
NSURL *url=[request URL];
if([url.path isEqualToString:@"/wr/qx"]){
//做点什么什么事儿
}
}
//请求完毕 但是服务端响应错误,没有成功
-(void)webView:(UIWebView )webView didFailLoadWithError:(NSError )error{
NSURLRequest *request=[webView request];
NSURL *url=[request URL];
NSLog(@"请求url=%@,错误=%@",[url absoluteString],error);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
WKWebView
@interface QXWKWebViewController ()
pragma mark -KVO 系统调用方法
-(void)observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary
pragma mark -获取手机的相关信息
-(void)deviceInfos{
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
// app名称
NSString *app_Name = [infoDictionary objectForKey:@"CFBundleDisplayName"];
// app版本
NSString *app_Version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
// app build版本
NSString *app_build = [infoDictionary objectForKey:@"CFBundleVersion"];
NSString * appXcodeStr =[infoDictionary objectForKey:@"DTXcode"];//Xcode 版本
NSString * appSDKNameStr = [infoDictionary objectForKey:@"DTSDKName"];//SDK 的版本
//手机别名: 用户定义的名称
NSString* userPhoneName = [[UIDevice currentDevice] name];
NSLog(@"手机别名: %@", userPhoneName);
//设备名称
NSString* deviceName = [[UIDevice currentDevice] systemName];
NSLog(@"设备名称: %@",deviceName );
//手机系统版本
NSString* phoneVersionStr = [[UIDevice currentDevice] systemVersion];
CGFloat phoneVersionFloat=[phoneVersionStr floatValue];
// //手机序列号
// if(phoneVersionFloat<7.0){7.0以后这个方法给禁了
// NSString* identifierNumber = [UIDevice currentDevice] uniqueIdentifier];
// NSLog(@”手机序列号: %@”,identifierNumber);
// }
//可以用OpenUDID替代手机序列号的唯一性:https://github.com/ylechelle/OpenUDID
NSLog(@"手机系统版本: %@", phoneVersionStr);
//手机型号
NSString* phoneModel = [[UIDevice currentDevice] model];
NSLog(@"手机型号: %@",phoneModel );
//地方型号 (国际化区域名称)
NSString* localPhoneModel = [[UIDevice currentDevice] localizedModel];
NSLog(@"国际化区域名称: %@",localPhoneModel );
}
pragma mark -WKNavigationDelegate 代理方法的开始
/**
request 来了前段先过滤一下 看看是否确定要真的去Request
*/
-(void)webView:(WKWebView )webView decidePolicyForNavigationAction:(WKNavigationAction )navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
//发送请求之前决定是否开启跳转 相当于didShuldeStart
//无论你是否拦截 都有调用block 否则会报错
NSLog(@"decidePolicyForNavigationAction");
NSURL *url=navigationAction.request.URL;
if(navigationAction.navigationType==WKNavigationTypeLinkActivated){
//&&![url.host containsString:@"我们公司规定的host"]一般还要加上不是我们公司内部的host
//这样都是属于外域链接 需要手动跳转
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
//不允许web内跳 不继续request
decisionHandler(WKNavigationActionPolicyCancel);
}else{
//跳转 去继续request
decisionHandler(WKNavigationActionPolicyAllow);
}
}
/**
允许request后开始这一步 向服务器发起request
*/
-(void)webView:(WKWebView )webView didStartProvisionalNavigation:(WKNavigation )navigation{
//开启跳转
NSLog(@"didStartProvisionalNavigation");
}
/**
服务器收到发起的request 先过滤一下 确定是否搭理这个request 给个回应 算是第一次握手
*/
-(void)webView:(WKWebView )webView decidePolicyForNavigationResponse:(WKNavigationResponse )navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
//收到服务端的握手回应 决定是否提交跳转
NSURL *url=navigationResponse.response.URL;
NSLog(@”decidePolicyForNavigationResponse”);
//无论你是否拦截 都有调用block 否则会报错
// if([url.host containsString:@”本公司的host”]){//就搭理它 接收request 给个回应
// decisionHandler(WKNavigationActionPolicyAllow);
// return;
// }else{
//
// decisionHandler(WKNavigationActionPolicyCancel);
// }
decisionHandler(WKNavigationActionPolicyAllow);
}
-(void)webView:(WKWebView )webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation )navigation{
//这个不一定会被调用
NSLog(@"didReceiveServerRedirectForProvisionalNavigation");
}
/**
第一次握手成功 服务端给了回应开始真正提交request
*/
-(void)webView:(WKWebView )webView didCommitNavigation:(WKNavigation )navigation{
//握手成功确定提交跳转
NSLog(@"didCommitNavigation");
}
/**
request成功 web加载成功 完成
*/
-(void)webView:(WKWebView )webView didFinishNavigation:(WKNavigation )navigation{
//完成跳转
NSLog(@"didFinishNavigation");
//模拟三秒加载时间
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[webView.scrollView.mj_header endRefreshing];
});
}
/**
request失败 web加载失败
*/
-(void)webView:(WKWebView )webView didFailNavigation:(WKNavigation )navigation withError:(NSError *)error{
//跳转失败
NSLog(@"didFailNavigation");
//模拟三秒加载时间
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[webView.scrollView.mj_header endRefreshing];
});
}
pragma mark - WKUIDelegate 接口的两个方法
if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_9_0
- (void)webViewDidClose:(WKWebView *)webView {
NSLog(@”%s”, FUNCTION);
}
endif
// 解决创建一个新的WebView(标签带有 target=’_blank’ 时,导致WKWebView无法加载点击后的网页的问题。)
-(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;
}
-(void)webView:(WKWebView )webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge )challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
//SSL验证 如果没有就走默认的
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling,nil);
NSLog(@"didReceiveAuthenticationChallenge");
}
-(void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
//页面内容完全加载完毕 不一定会被调用
NSLog(@"webViewWebContentProcessDidTerminate");
}
pragma mark WKScriptMessageHandler接口的方法
-(void)userContentController:(WKUserContentController )userContentController didReceiveScriptMessage:(WKScriptMessage )message{
//我们通过[userContentController addScriptMessageHandler:self name:@"AppModel"];给JS注册了一个别名类
//当JS通过这个别名对象QXApp调用OC代码的时候 这个方法就会接收到信息
NSString *name=message.name;//就是我们注册给js的别名
if([name isEqualToString:@"AppModel"]){
NSLog(@"js 消息内容:%@", message.body);
}
}
(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];[self.navigationController setNavigationBarHidden:YES animated:YES];
[self setNeedsStatusBarAppearanceUpdate];
}(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];[self.navigationController setNavigationBarHidden:NO animated:YES];
}(BOOL)prefersStatusBarHidden
{
return YES;
}
/*
pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end