In my iOS app I want to display several items that contain some HTML. For this I've build a NewsListViewController that contains a UITableView. For that UITableView (outlet: newsListTableView) I created a custom UITableViewCell called NewsTableViewCell that will be load into it via delegates. The NewsTableViewCell holds a WKWebKit control that will display my HTML. This is my code:
NewsListViewController.swift
import UIKit
class NewsListViewController: UIViewController {
@IBOutlet weak var newsListTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.newsListTableView.delegate = self
self.newsListTableView.dataSource = self
self.newsListTableView.register(UINib(nibName: "NewsTableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
}
}
extension NewsListViewController: UITableViewDelegate {
}
extension NewsListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 300.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NewsTableViewCell
cell.titleText = "My Title"
cell.dateTimeText = "01.01.1998"
cell.contentHtml = "<html><head><meta name=\"viewport\" content=\"initial-scale=1.0\" /><style>html, body { margin: 0; padding: 0; font-size: 15px; }</style></head><body><b>Hello News</b><br />Hello News<br />Hello News<br />Hello News</body></html>"
return cell
}
}
NewsTableViewCell.xib
My WKWebKit (outlet: newsContentPreviewWebView) is lying inside of an UIView (outlet: newsContentViewContainer) with proper constraints to grow with the UIView. My UIView comes with proper constraints to grow with the whole cell, too.
NewsTableViewCell.swift
import UIKit
import WebKit
@IBDesignable class NewsTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var dateTimeLabel: UILabel!
@IBOutlet weak var newsContentViewContainer: UIView!
@IBOutlet weak var newsContentPreviewWebView: WKWebView!
@IBInspectable var titleText: String? {
get {
return self.titleLabel.text
}
set(value) {
self.titleLabel.text = value
}
}
@IBInspectable var dateTimeText: String? {
get {
return self.dateTimeLabel.text
}
set(value) {
self.dateTimeLabel.text = value
}
}
@IBInspectable var contentHtml: String? {
get {
return nil
}
set(value) {
self.newsContentPreviewWebView.loadHTMLString(value ?? "", baseURL: nil)
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.newsContentPreviewWebView.navigationDelegate = self
}
}
extension NewsTableViewCell: WKNavigationDelegate {
}
This is the result:
Now, I want the cells to be as small as possible, but still displaying the full WKWebKit content.
When I remove ...
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return 300.0
}
... from NewsListViewController.swift the cell height will only respect the labels above the WKWebKit control:
I think this is happening, because my WKWebKit's content is not loaded when the app sizes the cells.
I tried to overcome this problem by listening to the WKWebKit's >> did finish navigation << delegate call in my NewsTableViewCell.swift and resizing the parent view and whole cell height like so:
extension NewsTableViewCell: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
let h: CGFloat = height as! CGFloat
let newsContentFrame: CGRect = self.newsContentViewContainer.frame
self.newsContentViewContainer.frame = CGRect(x: newsContentFrame.minX, y: newsContentFrame.minY, width: newsContentFrame.width, height: h)
let tableCellFrame: CGRect = self.frame
self.frame = CGRect(x: tableCellFrame.minX, y: tableCellFrame.minY, width: tableCellFrame.width, height: tableCellFrame.height + h)
})
}
})
}
}
This is the result:
The cells are overlapping which indicates that I'm trying to solve this in the wrong way.
What is the correct way to auto size the custom table cells to fit all its content including the content of the WKWebKit?
The problem is you never give to your cells a height. Setting the frame by hand
self.frame = CGRect(x: tableCellFrame.minX, y: tableCellFrame.minY, width: tableCellFrame.width, height: tableCellFrame.height + h)
is bad. You should always use the dedicated delegate method or the estimated row height technique (Using Auto Layout in UITableView for dynamic cell layouts & variable row heights).
The problem is sticky : you need to wait for the webview to load to calculate its height. But you need its height when heightForCell
is called so before the content of the webview is loaded.
I see 5 solutions :
completionHandler
is called, you call a delegate (your viewcontroller for instance) that will reload the corresponding cell and giving it its right height just calculated. This solution is simple but before each loading, you need to give at your cell a wrong height. So, you could define a loading state in your cell but you will always have a little glitch when a cell is about to appear.Operation
for each of your cells that will wait for the html of the cell to load and evaluate the given javascript. When all the operations are done, you reload your tableview