11

I've been struggling with this for two days, so I come hat in hand to the wise people of the internet.

I am showing an article as part of my UITableView. For this to display correctly, I need to give the delegate a height for the cell, which I want to be the same as the UIWebView's height, so I can disable scroll on the WebView and display the web content in its entirety as a static cell.

My first approach was to render it in the heightForRowAtIndexpathmethod, but this did obviously not work as I need the wait for the UIWebViewDelegate to tell me when the web view is fully loaded and has a height. After a while I found a working solution, which used the web view delegate to refresh the cell height when the web view was loaded.

The works fine until the screen size changes. Either from rotate or from full-screening my UISplitView. I forced an update on it in the didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation), but this causes it to flash about 10 times before settling into the correct height. I logged this change, and it seems the WebView is calling itself multiple times, causing a loop.

As seen in this log, starting from when I rotated the screen.

It flashes once every time it reloads, and as you can see, it reloads itself a bunch of times.

So. I need a way to show an entire web views content inside a uitableview, and reliably get the height when the screen size changes. If anyone has managed this in any way before, please tell me. I will give a bounty and my firstborn child to anyone who can resolve this, as it's driving me insane.

Here's my relevant code.

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    switch indexPath.row {
case 4:
        //Content
        print("Height for row called")
        return CGFloat(webViewHeight)
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    switch (indexPath.row){
//HTML Content View
    case 4:
        let cell = tableView.dequeueReusableCellWithIdentifier("ContentCell", forIndexPath: indexPath)
        var contentCell = cell as? ContentCell
        if contentCell == nil {
            contentCell = ContentCell()
        }
        contentCell?.contentWebView.delegate = self
        contentCell?.contentWebView.scrollView.userInteractionEnabled = false
        contentCell?.contentWebView.loadHTMLString((post?.contentHTML)!, baseURL: nil)
        print("Cell For row at indexpath called")
        return contentCell!
}
func webViewDidFinishLoad(webView: UIWebView) {
    updateHeight()
}

func updateHeight(){
    let webView = (self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 4, inSection: 0)) as! ContentCell).contentWebView
    if self.webViewHeight != Double(webView.scrollView.contentSize.height) {
        print("Previous WV Height = \(self.webViewHeight), New WV Height = \(webView.scrollView.contentSize.height)")
        self.webViewHeight = Double(webView.scrollView.contentSize.height)
        tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .Automatic)
    } else {
        return
    }
}

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
    print("rotated")
        self.updateHeight()
        //tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .Automatic)
}
Oscar Apeland
  • 6,422
  • 7
  • 44
  • 92
  • I've had this problem before, and since UIWebViews are generally a magic black box, I often end up using a workaround. Assuming your article is mostly text, consider displaying the HTML content natively through a UITextView, with your HTML as an attributed string. For a basic example of displaying HTML in a UITextView, look at this answer: http://stackoverflow.com/a/20996085/209855 – Aaron Ash Jan 19 '16 at 14:55
  • @AaronAsh I looked into this, but it seems that attributed labels with HTML content parse too slow for my use-case, in addition to not supporting Tables. Sadly... – Oscar Apeland Jan 19 '16 at 22:52
  • You shouldn't have much difficulty getting it into a tableview cell, take a look here: http://stackoverflow.com/a/18818036/209855 Also, I've heard people complain about slow HTML parsing, but never seen it in practice. Worth giving a shot still? – Aaron Ash Jan 20 '16 at 00:25
  • It looks like the app is animating the change in size, and calling updateHeight() multiple times during the animation. Put a breakpoint in updateHeight() to see where it is called from. I am guessing you have it in another place in your code as well. – fishinear Jan 22 '16 at 12:32
  • Sidenote: from your code I understand that there is only one cell in your table with an embedded tableView? I would handle the whole webview logic (including instantiation) in the VC then, and just add the webview as a subview of the cell when the cell comes on screen (using `willDisplayCell` for example) This would avoid reloading everything should the cell go offscreen, and waiting for the webview to display. – amadour Jan 24 '16 at 09:43

3 Answers3

1

I solved this by changing the .Automatic to .None in the row change animation. Its still a bad solution, but at least it doesn't flicker anymore.

Oscar Apeland
  • 6,422
  • 7
  • 44
  • 92
0

I would recommend that you calculate the web view height independently of the table view and store the dimension as part of the data itself and use it return in heightForRowAtIndexPath call. Its a easier that way since you don't have to deal with calculating the table height during table view display. When the html content is not loaded use a standard height and a message for the web view.

Pradeep K
  • 3,671
  • 1
  • 11
  • 15
-1

I don't see a problem in your implementation. Trey few things There are few things you can check

func webViewDidFinishLoad(webView: UIWebView) {
    updateHeight()
    //This function may get called multiple times depending on webpage. 
}

//Use
      self.tableView.beginUpdates()
      self.tableView.endUpdates()
//Instead of
 tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .None)

    func updateHeight(){
        let webView = (self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 4, inSection: 0)) as! ContentCell).contentWebView
        if self.webViewHeight != Double(webView.scrollView.contentSize.height)
        {
            print("Previous WV Height = \(self.webViewHeight), New WV Height = \(webView.scrollView.contentSize.height)")
            self.webViewHeight = Double(webView.scrollView.contentSize.height)
            self.tableView.beginUpdates()
            self.tableView.endUpdates()
//            tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0)], withRowAnimation: .None)
        } else {
            return
        }
    }

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation)
{
    print("rotated")
    let webView = (self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 4, inSection: 0)) as! ContentCell).contentWebView
    webView.reload();
}

You will need to reload webview on orientation change.

Amit Tandel
  • 883
  • 7
  • 16