YHExplorerView.m 12.8 KB
//
//  YHExplorerView.m
//  YohoExplorerDemo
//
//  Created by gaoqiang xu on 3/2/15.
//  Copyright (c) 2015 gaoqiang xu. All rights reserved.
//

#import "YHExplorerView.h"

@interface YHExplorerView ()
<UIGestureRecognizerDelegate>
@property (readwrite, strong, nonatomic) YHExplorerViewController *webViewController;
@property (strong, nonatomic) NSString *url;
@property (assign, nonatomic) BOOL isLoaded;
@property (strong, nonatomic) UITapGestureRecognizer *tapGesture;

@end

@implementation YHExplorerView

- (instancetype)initWithFrame:(CGRect)frame withNaviBar:(UIView *)naviBar
{
    self = [self initWithFrame:frame];
    self.currentNaviBar = naviBar;
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self _initialize];
    }
    
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self _initialize];
    }
    
    return self;
}

- (void)_initialize {
    self.isLoaded = NO;
    self.enablePictureTapGesture = NO;
}

- (void)setUrl:(NSString *)url
{
    // 检验url是否能正常转成NSURL,防止webview不能加载
    BOOL isValidUrl = ([NSURL URLWithString:url] != nil);
    
    if (url.length > 0
        && !isValidUrl) {
        // 如果url里面含有需要转码的字符,则这里进行一次编码
        _url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    } else {
        _url = url;
    }
}

- (void)didMoveToSuperview
{
    if (!self.superview) {
        return;
    }
}

- (void)didMoveToWindow
{
    if (!self.window) {
        if (_tapGesture) {
            [self removeGestureRecognizer:self.tapGesture];
        }
        return;
    }
    
    if (!self.isLoaded) {
        [self addSubview:self.webViewController.view];
        self.isLoaded = YES;
        if (self.url.length > 0) {
            [self.webViewController loadWebUrl:self.url];
        }
    }
    
    if (self.enablePictureTapGesture) {
        [self addGestureRecognizer:self.tapGesture];
        [self.tapGesture requireGestureRecognizerToFail:self.webView.scrollView.panGestureRecognizer];
    }
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    
    if (self.isLoaded) {
        self.webViewController.view.frame = self.bounds;
    }
}

#pragma mark - Public
- (void)loadWebUrl:(NSString *)url
{
    self.url = url;
    if (self.isLoaded) {
        [self.webViewController loadWebUrl:self.url];
    }
}

- (void)loadHTMLString:(NSString *)html
{
    if (self.isLoaded) {
        [self.webViewController loadHTMLString:html];
    }
}

- (void)loadHTMLString:(NSString *)html baseURL:(nullable NSURL *)baseURL
{
    if (self.isLoaded) {
        [self.webViewController loadHTMLString:html baseURL:baseURL];
    }
}

- (void)setProgressBarColor:(UIColor *)color
{
    [self.webViewController setProgressBarColor:color];
}

#pragma mark - Override Setter & Getter
- (YHExplorerViewController *)webViewController
{
    if (!_webViewController) {
        _webViewController = [[YHExplorerViewController alloc] initWithCurrentNaviBar:self.currentNaviBar];
    }
    return _webViewController;
}

- (UIView *)webView
{
    return self.webViewController.webView;
}

- (UITapGestureRecognizer *)tapGesture {
    if (!_tapGesture) {
        _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hanldeTapGesture:)];
        _tapGesture.delegate = self;
    }
    return _tapGesture;
}

#pragma mark - Setters

- (void)setDelegate:(id<YHExplorerDelegate>)delegate
{
    self.webViewController.delegate = delegate;
}

- (id<YHExplorerDelegate>)delegate
{
    return self.webViewController.delegate;
}

- (void)setEnableProgressBar:(BOOL)enableProgressBar
{
    self.webViewController.currentNaviBar = self.currentNaviBar;
    self.webViewController.progressBarEnabled = enableProgressBar;
    
}

- (void)setEnablePictureTapGesture:(BOOL)enablePictureTapGesture {
    _enablePictureTapGesture = enablePictureTapGesture;
    
    if (!self.window) {
        return;
    }
    
    if (enablePictureTapGesture) {
        if (self.superview) {
            [self addGestureRecognizer:self.tapGesture];
            [self.tapGesture requireGestureRecognizerToFail:self.webView.scrollView.panGestureRecognizer];
        }
    } else {
        if (_tapGesture) {
            [self removeGestureRecognizer:self.tapGesture];
            self.tapGesture = nil;
        }
    }
}

- (BOOL)enableProgressBar
{
    return self.webViewController.progressBarEnabled;
}

#pragma mark - Private
- (NSString *)_getImageScriptStringWithPoint:(CGPoint)p {
    NSString *string = [NSString stringWithFormat:@"var imageElement = document.elementFromPoint(%f, %f);\
                        var rect = imageElement.getBoundingClientRect();\
                        var frame = {x:rect.left, y:rect.top, width:rect.width, height:rect.height};\
                        var className = imageElement.className;\
                        var dataSet = { url:imageElement.src, onclick:imageElement.getAttribute('onclick'), displayFrame:frame, class:className, tagName:imageElement.tagName, id:imageElement.id };\
                        JSON.stringify(dataSet);", p.x, p.y];
    return string;
}

- (NSString *)_getJsStringToValidateGestureAreaById {
    if (self.constraintIdsForPictureTapGesture.count == 0) {
        return nil;
    }
    
    NSMutableString *jsString = [[NSMutableString alloc] initWithString:@"var frameArray = [];"];
    for (NSUInteger i=0; i<self.constraintIdsForPictureTapGesture.count; i++) {
        [jsString appendFormat:@"var ele%zd = document.getElementById('%@');var rect%zd = ele%zd.getBoundingClientRect();var frame%zd = {x:rect%zd.left, y:rect%zd.top, width:rect%zd.width, height:rect%zd.height};frameArray.push(frame%zd);", i, self.constraintIdsForPictureTapGesture[i], i, i, i, i, i, i, i, i];
    }
    [jsString appendString:@"JSON.stringify(frameArray);"];
    return jsString;
}

- (NSString *)_getJsStringToValidateGestureAreaByClass {
    if (self.constraintClassesForPictureTapGesture.count == 0) {
        return nil;
    }
    
    NSMutableString *jsString = [[NSMutableString alloc] initWithString:@"var frameArray = [];"];
    for (NSUInteger i=0; i<self.constraintClassesForPictureTapGesture.count; i++) {
        [jsString appendFormat:@"var ele%zd = document.getElementsByClassName('%@');for (var i=0;i<ele%zd.length;i++) {var ele = ele%zd[i]; var rect = ele.getBoundingClientRect();var frame = {x:rect.left, y:rect.top, width:rect.width, height:rect.height};frameArray.push(frame);}", i, self.constraintClassesForPictureTapGesture[i], i, i];
    }
    [jsString appendString:@"JSON.stringify(frameArray);"];
    return jsString;
}

#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if (gestureRecognizer == _tapGesture) {
        return YES;
    } else {
        return NO;
    }
}

#pragma mark - Gesture Handlers 
- (void)hanldeTapGesture:(UITapGestureRecognizer *)gesture {
    if (gesture == _tapGesture) {
        CGPoint touchPoint = [gesture locationInView:self.webView];
        CGFloat displayWidth = [[self.webViewController stringByEvaluatingJavaScriptFromString:@"window.innerWidth"] floatValue];
        CGFloat scale = self.webView.frame.size.width / displayWidth;
        // 如果webview是zoom状态,把对应的比例除去
        touchPoint.x /= scale;
        touchPoint.y /= scale;
        
        BOOL validArea = YES;
        BOOL validIdArea = YES;
        BOOL validClassArea = YES;
        
        if (self.constraintIdsForPictureTapGesture.count) {
            validIdArea = NO;
            NSString *frameArrayString = [self.webViewController stringByEvaluatingJavaScriptFromString:[self _getJsStringToValidateGestureAreaById]];
            if (frameArrayString.length) {
                NSData *resultData = [frameArrayString dataUsingEncoding:NSUTF8StringEncoding];
                NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:resultData
                                                                     options:0
                                                                       error:nil];
                if (jsonArray.count == self.constraintIdsForPictureTapGesture.count) {
                    for (NSDictionary *dic in jsonArray) {
                        NSNumber *rectX = dic[@"x"];
                        NSNumber *rectY = dic[@"y"];
                        NSNumber *rectWidth = dic[@"width"];
                        NSNumber *rectHeight = dic[@"height"];
                        CGRect rect = CGRectMake(rectX?rectX.doubleValue:0, rectY?rectY.doubleValue:0, rectWidth?rectWidth.doubleValue:0, rectHeight?rectHeight.doubleValue:0);
                        if (CGRectContainsPoint(rect, touchPoint)) {
                            validIdArea = YES;
                            break;
                        }
                    }
                }
            }
        }
        
        if (self.constraintClassesForPictureTapGesture.count) {
            validClassArea = NO;
            NSString *frameArrayString = [self.webViewController stringByEvaluatingJavaScriptFromString:[self _getJsStringToValidateGestureAreaByClass]];
            if (frameArrayString.length) {
                NSData *resultData = [frameArrayString dataUsingEncoding:NSUTF8StringEncoding];
                NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:resultData
                                                                     options:0
                                                                       error:nil];

                for (NSDictionary *dic in jsonArray) {
                    NSNumber *rectX = dic[@"x"];
                    NSNumber *rectY = dic[@"y"];
                    NSNumber *rectWidth = dic[@"width"];
                    NSNumber *rectHeight = dic[@"height"];
                    CGRect rect = CGRectMake(rectX?rectX.doubleValue:0, rectY?rectY.doubleValue:0, rectWidth?rectWidth.doubleValue:0, rectHeight?rectHeight.doubleValue:0);
                    if (CGRectContainsPoint(rect, touchPoint)) {
                        validClassArea = YES;
                        break;
                    }
                }
            }
        }
        
        if (self.constraintIdsForPictureTapGesture.count > 0 && self.constraintClassesForPictureTapGesture.count > 0) {
            validArea = (validIdArea || validClassArea);
        } else if (self.constraintIdsForPictureTapGesture.count) {
            validArea = validIdArea;
        } else if (self.constraintClassesForPictureTapGesture.count) {
            validArea = validClassArea;
        }
        
        if (!validArea) {
            return;
        }
        
        NSString *imgURL = [self _getImageScriptStringWithPoint:touchPoint];
        NSString *resultString = [self.webViewController stringByEvaluatingJavaScriptFromString:imgURL];
        
        if (resultString.length) {
            
            NSData *resultData = [resultString dataUsingEncoding:NSUTF8StringEncoding];
            
            NSDictionary *json = [NSJSONSerialization JSONObjectWithData:resultData
                                                                 options:0
                                                                   error:nil];
            
            if (json) {
                NSString *tagName = [json[@"tagName"] lowercaseString];
                NSString *onClick = json[@"onclick"];
                BOOL isJumpUrl = [onClick isKindOfClass:NSString.class] && [onClick isEqualToString:@"onclick"];
                if ([tagName isEqualToString:@"img"]
                    && !isJumpUrl
                    && [self.delegate respondsToSelector:@selector(explorer:didClickPicture:)]) {
                    [self.delegate explorer:self.webViewController didClickPicture:json];
                }
            }
        }
    }
}

#pragma mark - Method Forwarding
- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ([super respondsToSelector:aSelector]) {
        return YES;
    }
    
    if ([self.webViewController respondsToSelector:aSelector]) {
        return YES;
    }
    
    return NO;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if (self.webViewController && [self.webViewController respondsToSelector:aSelector]) {
            return [(NSObject *)self.webViewController methodSignatureForSelector:aSelector];
        }
    }
    
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if (self.webViewController && [self.webViewController respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:self.webViewController];
    }
}

//移除滚动条
- (void)removeProgress
{
    [self.webViewController removeProgress];
}

@end