4
votes

I have a UITableViewCell that has a UIWebView inside it. Data I load from the server has several comments containing rich HTML content(text, images, links, etc.) As the cell contains UIWebView, I have to wait till the entire content is loaded to get the height of the tableViewCell and update it.

The issue I'm running into is that as I scroll down the cells get updated and the whole view jerks and flickers. Basically, I'm looking for a way to preload the webviews to get their height beforehand. Also, this jerking only happens until all the cells have loaded the content since I'm storing the height of each cell locally.

Here's an example of the jittery experience.

Code of cellForRowIndex

open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(indexPath: indexPath) as LiMessageDetailTableViewCell
    cell.delegate = self
    cell.cellModel = LiMessageDetailTableViewCellModel(data: messageObject.originalMessage, index: indexPath)
    cell.heightOfWebView = messageObject.contentHeightsOriginalMessage[indexPath.row]
    if messageObject.contentHeightsOriginalMessage[indexPath.row] != 0.0 {
        cell.updateHeightConstrain()
    }
    return cell
}

WebView delegate method in TableViewCell

 //MARK:- WEBVIEW DELEGATE
    func webViewDidFinishLoad(_ webView: UIWebView) {
     //This section resizes the webview according to the new content loaded.
        var mframe = webView.frame
        mframe.size.height = 1
        webView.frame = mframe
        let fittingSize = webView.sizeThatFits(.zero)
        mframe.size = fittingSize
        webView.frame = mframe
    /**
    I found that javascript gives a more accurate height when images are
    included since they take more time loading than the normal content and 
    hence sizeThatFits does not always return proper result.
    **/
        let heightInString = webView.stringByEvaluatingJavaScript(from: "document.body.scrollHeight") ?? ""
        guard let heightInFloat = Float(heightInString) else { return }
        guard let index = cellModel?.indexPath else {return}
        constrainHeightOfWebView.constant = fittingSize.height
        guard let cellType = cellModel?.messageType  else { return }
        delegate?.updateHeight(index: index, newHeight: CGFloat(heightInFloat), cellType: cellType)
    }

Delgate method which updates the height

func updateHeight(index: IndexPath, newHeight: CGFloat, cellType: LiMessageType) {
    switch cellType {
    case .originalMessage:
        if msgObj.contentHeightsOriginalMessage[index.row] != 0.0 && msgObj.contentHeightsOriginalMessage[index.row] == newHeight {
            return
        }
        msgObj.contentHeightsOriginalMessage[index.row] = newHeight
   .....
    }
    self.tableView.beginUpdates()
    self.tableView.endUpdates()
}

Another issue is that if the webview contains heavy images then the whole webview flickers as it reloads everytime the cell is dequeued, causing a bad experience.

Here's the example of webview with image reloading

The image issue resolves itself after some time as webview gets cached, but nevertheless a bad experience.

Is there a way to solve these issues?

1
Using UIWebView inside a reusable UITableViewCell is a bad idea at first. The reason being is that there are fairly less resources to trace if the UIWebView is loaded properly in case of Reusable UITableViewCell. If the HTML code is responsive, it is a cherry on top. Currently there is no breakthrough to this problem which many developers are facing since years. However, if you still get a solution please do share it.caffieneToCode
I know, but the data is stored in the form of HTML and that won't change soon, hence the UIWebView. Will definitely share if I get a solution.shek

1 Answers

3
votes

You can create an array of web views and store it in your view controller, preload their content and displaying some kind of spinner animation while they load. Once all the web views are done loading, you should be able to get the content height of the various web views.

You will likely have to modify your cells so they can receive a web view as a parameter; you pass the entire web view to the cell which will add it as a subview of the content.

In the cell prepareForReuse() function, make sure you remove any web view that was added so. As you do not wish to retain a previous web view when the cells are reused.