有一种我们经常能看到的页面,上方是图文混排的富文本内容,下方是评论列表。比如网易新闻详情页,简书文章详情页。它们是怎么实现的呢?通常是webView+tableView的组合,因为文章和新闻的编辑后台生成的就是html文本,用webView去渲染能最简单高效优美地呈现内容。

具体到实现细节,webView与native的交互方式,本地静态html模板缓存,图片占位等,每家都有自己的方案,有兴趣了解的话我会在以后的文章里介绍我们的方案。本文主要介绍我们是如何去嵌套这两个控件的。

简书的开发人员在《UIWebView与UITableView的嵌套方案》这篇文章里介绍了三种方案,其中第一种方案是很多人都会采用的——把webView作为tableView的headView,并在拿到webview实际内容高度的时候重设headView的高度。这样一来,滚动全交给tableView,把webview的滚动禁用掉,那么整体滑动就会非常和谐。

看上去很美,但是上面的做法是有代价的,当webview内容很多,多图多文,webview的frame.size.height会变得非常大,如果留意这时候的内存情况的话,相同的内容在size.height更大的webview里内存会高出很多。对于图文内容非常多的app来说,这个方案存在内存爆炸的隐患。

那么,如何在不改变webView高度的情况下,让整体滑动和谐,视觉上webview和tableView无缝衔接呢?

简书开发人员的做法是把tableView放在webView里,并禁用webView和tableView的滑动,完全自己去实现滑动功能,文章里有源码地址,有兴趣的可以去看看,处理起来挺复杂,而且实际应用中,tableView往往是分页加载的列表,这里的上滑加载更多还要去实现,并且在tableView内容变化之后还要去更新webView的size,他们的demo里没有提到这个。

放弃手写滑动,重新审视问题:有没有这样一种办法,不改变webView高度的情况下,通过滑动tableView来让浏览器里面的内容跟着滑动起来?有的呀!“透视”!

我们还是依靠tableView的headView,headView的高度跟着浏览器的内容高度而变,但是这个headView不直接设为webView,而是个高度等于浏览器高度,但本身透明的一个view,然后把webView放置在整个tableView的下面,这样webView的内容实际上就透视出来了,让人“看得到但摸不着”。

self.htmlWebView = [[UIWebView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height-50-44-STATUS_BAR_HEIGHT)];
_htmlWebView.scrollView.scrollsToTop = NO;
_htmlWebView.scrollView.scrollEnabled = NO;
_htmlWebView.delegate = self;
[_htmlWebView setBackgroundColor:[UIColor grayBackgroundColor]];
[_htmlWebView setOpaque:NO];
[self.view addSubview:_htmlWebView];
[self.view sendSubviewToBack:_htmlWebView];

self.tableHeadView = [[UIView alloc]initWithFrame:self.htmlWebView.frame];
 [_tableHeadView setBackgroundColor:[UIColor clearColor]];

self.tableView.tableHeaderView = _tableHeadView;
self.tableView.backgroundColor = [UIColor clearColor];复制代码

接下来的任务就是通过操作上层的tableView去滑动底层的webView,同样很简单,看代码。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if ([scrollView isKindOfClass:[UITableView class]]) {
        self.htmlWebView.scrollView.contentOffset = scrollView.contentOffset;
    }
}复制代码
- (void)_resizeHeadView {    
    [_tableHeadView setFrame:CGRectMake(0, 0, self.tableView.frame.size.width, self.webviewHeight)];

    [self.tableView beginUpdates];
    [self.tableView setTableHeaderView:self.tableHeadView];
    [self.tableView endUpdates];
}复制代码

顺便,提一下webview内容高度获取的问题,为了用户体验,我们不可能等到webView完全finishLoad才去更新headView的高度,可能有的团队的做法是根据内容加载的进度,动态去改变这个高度,但是这会带来页面内容哐哐哐往下掉的问题,所以我们项目的做法是由服务器下发带图片尺寸的html文本,文本请求是很快的,我们在页面一进来就能根据html文本计算出预期的内容高度,把headView的高度调整到位,其中这个计算交给js。

以下是webView通知native“已经计算好内容高度,去更新吧~”的代码。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

    if ([request.URL.scheme isEqualToString:@"bolomegetwebviewheight"]) {
        self.webviewHeight = [request.URL.host floatValue];
        if (!fEqualTo(CGRectGetHeight(self.htmlWebView.frame), self.webviewHeight)) {
            [self _resizeHeadView];
        }
        return NO;
    }

    return YES;
}复制代码