让你的WKWebView支持自动布局----Auto Layout

开篇语:

现在很多项目都用到了图文混排,考虑到后台传的数据格式一般都是HTML,WKWebView成了展示的首选组件。用过WKWebView的人都清楚,它本身是不支持自动布局的,这让我们这些用惯了自动布局的人来说望而却步。试想一下,如果可以灵活的在WKWebView和各种view之间灵活展示内容,将是一件多么令人兴奋的事情。

难点分析:

WKWebView 由于是异步绘制的,所有内容被绘制在一个UIScrollView上,我们需要实时得到它的size才行。那是不是说我们通过KVO监听scollview的contentSize的变化不就可以了吗?是的,理论上是这样,但是,事实是它返回的数据不够准确。原因就是HTML绘制的复杂性,可能掺杂各种js、图片,高度变化异常。

还有一种办法,预先在HTML加载是时候,js注入一个div,然后在适当的时机通过js方式获取网页的实际高度。直接上代码:

HXWKWebView.h

#import 

//wkwebview展示HTML内容
@interface HXWKWebView : UIView

- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

@end

HXWKWebView.m

#import "HXWKWebView.h"
#import 

@interface HXWKWebView ()
@property(nonatomic, strong) NSString *htmlString;
@property(nonatomic, assign) CGFloat contentHeight;
@property(nonatomic, strong) WKWebView *wkWebView;
/**
 *  自动布局的时候用的
 */
@property(nonatomic) CGFloat preferredMaxLayoutWidth;

@end

@implementation HXWKWebView

- (instancetype)init
{
    self = [super init];
    if (self) {
    }
    return self;
}

/**
 初始化
 */
- (void)initWebView {
    
    WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    wkWebConfig.userContentController = wkUController;
    //自适应屏幕宽度,注入js
    NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width,user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta); var lastDiv = document.createElement('div');lastDiv.id = 'last-div'; document.getElementsByTagName('body')[0].appendChild(lastDiv)";
    WKUserScript *wkUserScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    [wkUController addUserScript:wkUserScript];
    
    self.wkWebView = [[WKWebView alloc]initWithFrame:self.bounds configuration:wkWebConfig];
    self.wkWebView.UIDelegate = self;
    self.wkWebView.navigationDelegate = self;
    self.wkWebView.opaque = NO;
    //这里做了一些关闭处理,可以根据自己的实际情况选择
    self.wkWebView.scrollView.scrollEnabled = NO;
    self.wkWebView.scrollView.showsVerticalScrollIndicator = NO;
    self.wkWebView.scrollView.scrollsToTop = NO;
    self.wkWebView.scrollView.userInteractionEnabled = NO;
    if (@available(iOS 11.0,*)) {
        self.wkWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
    self.wkWebView.scrollView.bounces = NO;
    self.wkWebView.backgroundColor = [UIColor clearColor];
    
    [self addSubview:self.wkWebView];
    
    [self addWebViewObserver];
    
    [self.wkWebView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(UIEdgeInsetsZero);
    }];
    
    [self.wkWebView loadHTMLString:self.htmlString baseURL:nil];
}

- (void)addWebViewObserver {
    [self.wkWebView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:@"WebKitContext"];
}

- (void)removeWebViewObserver {
    [self.wkWebView.scrollView removeObserver:self forKeyPath:@"contentSize" context:@"WebKitContext"];
}

#pragma mark - KVO

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (self.wkWebView.isLoading && [keyPath isEqualToString:@"contentSize"]) {
        CGFloat height = self.wkWebView.scrollView.contentSize.height;
        NSLog(@"scrollViewHeight = %f", height);
        self.contentHeight = height;
        [self invalidateIntrinsicContentSize];
    }
}

-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {

    [self.wkWebView evaluateJavaScript:@"document.getElementById(\"last-div\").offsetTop" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        CGFloat height = [result doubleValue];
        NSLog(@"scrollViewHeight = %f", height);
        self.contentHeight = height;
        [self invalidateIntrinsicContentSize];
    }];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    if (!self.wkWebView.isLoading) {
        [self.wkWebView evaluateJavaScript:@"document.getElementById(\"last-div\").offsetTop" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
            CGFloat height = [result doubleValue];
            NSLog(@"scrollViewHeight = %f", height);
            self.contentHeight = height;
            [self invalidateIntrinsicContentSize];
        }];
    }
}

- (CGSize)intrinsicContentSize {
    return CGSizeMake(self.wkWebView.scrollView.contentSize.width, self.contentHeight);
}

- (void)dealloc {
    [self removeWebViewObserver];
}

- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL{
    
    self.htmlString = string;
    [self initWebView];
}

@end

这里使用双保险获取WKWebView的高度,结合使用intrinsicContentSize属性,保障任何时候(例如屏幕旋转)等情况都可以触发自动布局,非常实用。

有没有遇到白屏问题?没有。注意,以上方式是将HTML内容一次性展示,所以不存在scrollview不刷新内容问题。

结束语:

大家知道,WKWebView已经比UIWebView强了N多倍(这里我就不细说了,网上资料一大堆),但是有一个缺憾依然存在,就是它从初始化到内容渲染的时间还是很长。如果你的页面只有一个WKWebView可能觉察不出来延迟,当多了之后,会明显感觉到卡顿。这可不是auto layout的黑锅哦,谁让init只能在主线程中执行。下一篇,我将介绍使用预加载、预初始化来优化次问题,尽请期待。

--------------

已更新,看这里:WKWebView预初始化

 

你可能感兴趣的:(iOS)