iOS技术点iOS开发(OC)iOS 开发继续加油

自己封装的WKWebView(OC)

2017-04-21  本文已影响994人  爱恨的潮汐

自己封装的HtmlWKWebView(OC)

一、.h文件里
/*
  (1)适用于HTML解析和网页链接的解析,不适用与与JS的交互,因为代理方法控制器里不走,除非重写代理方法
  (2)WKWebView的封装所有属性,包括进度条、长按保存图片、图片自适应手机、WKWebView的头部控件、尾部控件
 (3)使用时一定要先设置完头部和尾部控件,在开始加载网页
*/
#import <WebKit/WebKit.h>

@interface HtmlWKWebView : WKWebView

//wkWebview网页
@property(nonatomic,strong)WKWebView * wkWebview;

//头部视图
@property(nonatomic,strong)UIView * headerView;
//尾部视图
@property(nonatomic,strong)UIView * footView;

//HTML
@property(nonatomic,strong)NSString * htmlString;

//移除KVO
@property(nonatomic,strong)NSString * remoDeallocKvo;


//与JS有交互的WKWebViewConfiguration不会走Init方法需要手动调用创建UI
@property(nonatomic,strong)NSString * actionJS;


@end

二、.m文件里
#import "HtmlWKWebView.h"
//图片�浏览器控制器
#import "JZAlbumViewController.h"
@interface HtmlWKWebView()<WKNavigationDelegate,UIGestureRecognizerDelegate>
//图片链接数组
@property (strong, nonatomic)NSMutableArray * mArrayUrl;
//进度条
@property (strong, nonatomic) UIProgressView *progressView;
@end

@implementation HtmlWKWebView

// 在initWithFrame:方法中添加子控件
- (instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        //必须在初始化时创建否则会崩溃
        [self createUI];
        
    }
    return self;
}

//和JS有交互的
-(void)setActionJS:(NSString *)actionJS{
    _actionJS  = actionJS;
    //因为和JS有交互的WKWebViewConfiguration不走inint方法,所有手动调用
     [self createUI];
}

//注册KVO开启进度条
-(void)setWkWebview:(WKWebView *)wkWebview{
    _wkWebview = wkWebview;
    self.progressView.hidden = NO;
//    NSLog(@"wkWebview:%@",self.wkWebview);
    // KVO,监听webView属性值得变化(estimatedProgress,title为特定的key)
    [self.wkWebview addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}

//得到html
-(void)setHtmlString:(NSString *)htmlString{
    _htmlString = htmlString;
    
    
    // reSizeImageWithHTML设置头样式
    [self loadHTMLString:[self reSizeImageWithHTML:self.htmlString] baseURL:nil];

    
}

//创建UI
-(void)createUI{
      //打开垂直滚动条
     self.scrollView.showsVerticalScrollIndicator=YES;
    //关闭水平滚动条
     self.scrollView.showsHorizontalScrollIndicator=NO;
    //设置滚动视图的背景颜色
    self.scrollView.backgroundColor = [UIColor groupTableViewBackgroundColor];
    self.navigationDelegate = self;
    //两条属性关闭黑影
    self.backgroundColor = [UIColor clearColor];
    self.opaque = NO;
    

    // 添加头部
    _headerView = [[UIView alloc] init];
    _headerView.backgroundColor = [UIColor whiteColor];
    _headerView.userInteractionEnabled = YES;
    [self.scrollView addSubview:_headerView];
    
    
    //添加脚步
    _footView = [[UIView alloc] init];
    //开始加载时不设置脚步坐标
    _footView.frame = CGRectZero;
    _footView.backgroundColor = [UIColor whiteColor];
    _footView.userInteractionEnabled = YES;
    //先影藏
    _footView.hidden = YES;
    [self.scrollView addSubview:_footView];
    
    
    //添加长按手势
    self.userInteractionEnabled = YES;
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
    longPress.allowableMovement = 20;
    longPress.minimumPressDuration = 1.0f;
    longPress.delegate = self;
    [self addGestureRecognizer:longPress];
    
    if (self.actionJS.length>0) {
        //不创建点击手势,不然点击的时候又弹框有出现图片浏览的
        
    }else{
        //添加点击手势(点击查看大图)
        UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapPress:)];
        tap.delegate = self;
        [self addGestureRecognizer:tap];
        
    }
    
    
    // UIProgressView初始化
    self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
    self.progressView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 2);
    self.progressView.trackTintColor = [UIColor clearColor]; // 设置进度条的色彩
//    self.progressView.progressTintColor = [UIColor blueColor];
    // 设置初始的进度,防止用户进来就懵逼了(微信大概也是一开始设置的10%的默认值)
    self.progressView.hidden = YES;
    [self.progressView setProgress:0.1 animated:YES];
    [self addSubview:self.progressView];



}
//由于后台返回的HTML代码里……你懂的,所以只能添加样式来适配屏幕,下面的方法可以调节文字和图片大小来适配屏幕。(删者必死)
- (NSString *)reSizeImageWithHTML:(NSString *)html {
    // 顶部留出空白(用来创建头部视图):<p style='padding-top:200px;'></p>   底部留出空白(用力啊创建尾部视图):<p style='padding-bottom:200px;'></p>
    //设置头部、尾部空白和设置html图片自适应手机
    return [NSString stringWithFormat:@"<p style='padding-top:%fpx;'></p><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0'><meta name='apple-mobile-web-app-capable' content='yes'><meta name='apple-mobile-web-app-status-bar-style' content='black'><meta name='format-detection' content='telephone=no'><style type='text/css'>img{width:%fpx}</style>%@<p style='padding-bottom:%fpx;'></p>",self.headerView.frame.size.height, SCREEN_WIDTH - 15, html,self.footView.frame.size.height];
}


//页面加载完后获取高度,设置脚,注意,控制器里不能重写代理方法,否则这里会不执行
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    
    // 不执行前段界面弹出列表的JS代码,关闭系统的长按保存图片
    [self evaluateJavaScript:@"document.documentElement.style.webkitTouchCallout='none';" completionHandler:nil];
    
    // WKWebView禁止放大缩小(捏合手势)
    NSString *injectionJSString = @"var script = document.createElement('meta');"
    "script.name = 'viewport';"
    "script.content=\"width=device-width, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\";"
    "document.getElementsByTagName('head')[0].appendChild(script);";
    [webView evaluateJavaScript:injectionJSString completionHandler:nil];
 
    //获取网页高度
    [webView evaluateJavaScript:@"document.body.offsetHeight;" completionHandler:^(id Result, NSError * error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            CGFloat documentHeight = [Result doubleValue]+15.00;
//            NSLog(@"高度%f",documentHeight);
            //            [DWBToast showCenterWithText:[NSString stringWithFormat:@"%f",documentHeight]];
            if (self.htmlString.length>0) {
                //啥也不干
            }else{
                documentHeight = 0.0;
            }
            
            _footView.frame = CGRectMake(0, documentHeight-self.footView.frame.size.height, SCREEN_WIDTH, self.footView.frame.size.height);
            //加载完设置好坐标打开
            _footView.hidden = NO;

            
        });
    }];
    
    
    //网页加载完成后通过js获取htlm中所有图片url
    [self getImageUrlByJS:self];
    
    //清除WK缓存,否则H5界面跟新,这边不会更新
    [self remoViewCookies];
    

}

//清除WK缓存,否则H5界面跟新,这边不会更新
-(void)remoViewCookies{
    
    
    if ([UIDevice currentDevice].systemVersion.floatValue>=9.0) {
        //        - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation 中就成功了 。
        //    然而我们等到了iOS9!!!没错!WKWebView的缓存清除API出来了!代码如下:这是删除所有缓存和cookie的
        NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
        //// Date from
        NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
        //// Execute
        [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
        }];
    }else{
        //iOS8清除缓存
        NSString * libraryPath =  NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
        NSString * cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
        [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:nil];
    }
    

}



- (void)handleLongPress:(UILongPressGestureRecognizer *)sender{
    if (sender.state != UIGestureRecognizerStateBegan) {
        return;
    }
    CGPoint touchPoint = [sender locationInView:self];
    // 获取长按位置对应的图片url的JS代码
    NSString *imgJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y];
    // 执行对应的JS代码 获取url
    [self evaluateJavaScript:imgJS completionHandler:^(id _Nullable imgUrl, NSError * _Nullable error) {
        if (imgUrl) {
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgUrl]];
            UIImage *image = [UIImage imageWithData:data];
            if (!image) {
                NSLog(@"读取图片失败");
                return;
            }
            //获取到图片image
            
            //保存图片
            [AlertViewTool AlertWXSheetToolWithTitle:nil otherItemArrays:@[@"保存图片"] ShowRedindex:-1 CancelTitle:@"取消" handler:^(NSInteger index) {
                if (index==0) {
                    //保存
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //保存图片到相册
                        UIImageWriteToSavedPhotosAlbum(image, self, @selector(imageSavedToPhotosAlbum:didFinishSavingWithError:contextInfo:), nil);
                        
                    });
                }
                
            }];
            
            
        }
    }];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES;
}
//保存照片到本地相册
- (void)imageSavedToPhotosAlbum:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    if(!error){
        //延迟显示,否则会移除
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            [MBProgressHUD showSuccess:@"保存成功"];
            
        });
        
    }else{
        //延迟显示,否则会移除
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [MBProgressHUD showSuccess:@"保存失败"];
        });
        
    }
    
}


#pragma mark - KVO监听进度条
// 第三部:完成监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    if ([object isEqual:self] && [keyPath isEqualToString:@"estimatedProgress"]) { // 进度条
        
        CGFloat newprogress = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
        NSLog(@"网页进度:%f", newprogress);
        
        if (newprogress == 1) { // 加载完成
            // 首先加载到头
            [self.progressView setProgress:newprogress animated:YES];
            // 之后0.3秒延迟隐藏
            __weak typeof(self) weakSelf = self;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
                
                weakSelf.progressView.hidden = YES;
                [weakSelf.progressView setProgress:0 animated:NO];
            });
            
        } else { // 加载中
            self.progressView.hidden = NO;
            [self.progressView setProgress:newprogress animated:YES];
        }
    } else { // 其他
        
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

//暂时不用
//- (void)dealloc {
//    NSLog(@"移除了KVO");
//    // 最后一步:移除监听的KVO
//    [self.wkWebview removeObserver:self forKeyPath:@"estimatedProgress" context:nil];
//
//}

-(void)setRemoDeallocKvo:(NSString *)remoDeallocKvo{
    _remoDeallocKvo = remoDeallocKvo;
    NSLog(@"移除了KVO");
    // 最后一步:移除监听的KVO
    [self.wkWebview removeObserver:self forKeyPath:@"estimatedProgress" context:nil];
    
}


#pragma mark ========= 点击获取所有图片,并查看大图 ========================
//通过js获取htlm中图片url
-(void)getImageUrlByJS:(WKWebView *)wkWebView{

    //js方法遍历图片添加点击事件返回图片个数
    //这里是js,主要目的实现对url的获取
    static  NSString * const jsGetImages =
    @"function getImages(){\
    var objs = document.getElementsByTagName(\"img\");\
    var imgScr = '';\
    for(var i=0;i<objs.length;i++){\
    imgScr = imgScr + objs[i].src + '+';\
    };\
    return imgScr;\
    };";
    
    //用js获取全部图片,传质给js
    [wkWebView evaluateJavaScript:jsGetImages completionHandler:nil];

    //得到所有图片
    NSString *jsString = @"getImages()";
    
    [wkWebView evaluateJavaScript:jsString completionHandler:^(id Result, NSError * error) {
     
        NSString *resurlt=[NSString stringWithFormat:@"%@",Result];
        
        if([resurlt hasPrefix:@"+"]){
            
            resurlt=[resurlt substringFromIndex:1];
            
        }
        
       NSArray * array=[resurlt componentsSeparatedByString:@"+"];
        
        [self.mArrayUrl removeAllObjects];
        //添加到可变数组
        [self.mArrayUrl addObjectsFromArray:array];
        //移除最后一个元素(空白)
        [self.mArrayUrl removeLastObject];
        
//        NSLog(@"得到所有图片url:%@",self.mArrayUrl);
        
    }];
}

//点击手势
- (void)handleTapPress:(UITapGestureRecognizer *)sender{
    
    CGPoint touchPoint = [sender locationInView:self];
    // 获取长按位置对应的图片url的JS代码
    NSString *imgJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y];
    // 执行对应的JS代码 获取url
    [self evaluateJavaScript:imgJS completionHandler:^(id _Nullable imgUrl, NSError * _Nullable error) {
        if (imgUrl) {
            NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgUrl]];
            UIImage *image = [UIImage imageWithData:data];
            if (!image) {
                NSLog(@"读取图片失败");
                return;
            }
            //获取到图片image
            
            //图片大于0才创建
            if (self.mArrayUrl.count>0) {
                NSInteger currentIndex = 0;
                //得到索引
                for (int i= 0; i< self.mArrayUrl.count; i++) {
                    if ([imgUrl isEqual:self.mArrayUrl[i]]) {
                        //当前点击了第几张图片
                        currentIndex = i;
                    }
                }
                
                //控制器跳转
                JZAlbumViewController *jzAlbumVC = [[JZAlbumViewController alloc]init];
                //当前点击图片的索引
                jzAlbumVC.currentIndex = currentIndex;
                //imgArr可以为url数组, 可以为urlString 数组, 可以为二进制 UIImage 数组
                jzAlbumVC.imgArr = self.mArrayUrl;
                
                [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:jzAlbumVC animated:NO completion:nil];
                
            }else{
                //如果加载完后拿不到所有图片数组,就查看当前点击的图片
                //控制器跳转
                JZAlbumViewController *jzAlbumVC = [[JZAlbumViewController alloc]init];
                //当前点击图片的索引
                jzAlbumVC.currentIndex = 0;
                //imgArr可以为url数组, 可以为urlString 数组, 可以为二进制 UIImage 数组
                jzAlbumVC.imgArr = [NSMutableArray arrayWithArray:@[imgUrl]];
                
                [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:jzAlbumVC animated:NO completion:nil];
                
                
            }
        }
    }];
    
    
}


//图片数组
-(NSMutableArray *)mArrayUrl{
    if (!_mArrayUrl) {
        _mArrayUrl = [NSMutableArray array];
    }
    return _mArrayUrl;
}


#pragma mark======================WKWebView - alert不弹出(这是WKWebView相对于UIWebView的一个坑)===========================================
//WKWebView默认不响应js的alert()事件,如何可以开启alert权限呢?
//设置wkwebview.delegate = self;
//实现下面三个方法:
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
    
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    //    DLOG(@"msg = %@ frmae = %@",message,frame);
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }])];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = defaultText;
    }];
    [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];
    
    
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}


//WKWebView打开新界面 需要打开新界面是,WKWebView的代理WKUIDelegate方法
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    //    会拦截到window.open()事件.只需要我们在在方法内进行处理
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}



@end


上一篇下一篇

猜你喜欢

热点阅读