UIWebView與tableView巢狀的記憶體問題及解決方案

大力居士又叫小丸子發表於2017-11-16

有一種我們經常能看到的頁面,上方是圖文混排的富文字內容,下方是評論列表。比如網易新聞詳情頁,簡書文章詳情頁。它們是怎麼實現的呢?通常是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;
}複製程式碼

探討到這兒,後面有空寫個完整demo,還有其他方案嗎,歡迎交流~
PS.我和我家做前端的老公做了個公號,內容也會同時更新在那邊,歡迎來關注~

程式設計師夫婦的日常
程式設計師夫婦的日常

相關文章