I want to implement a method that looks something like this:
setCellHeightForIndexPath(someIndexPath, 80)
and then the table view cell at that index path will suddenly have a height of 80.
The reason I want to do this is because I want the height of the cell to be set to the height of the web view's content after it has finished loading the HTML. Since I can only get the web view's content size after it has finished loading, I can't just set the cell height right away.
See this question for more info.
So in the webViewDidFinishLoad
method, I can just get the web view's content height, set the web view's height to that, and call this method to set the cell's height.
It seems like that the cell height can only change when heightForRowAtIndexPath
is called. I think the method would use a similar approach as my answer to this question. I think I need to store an array of heights maybe? I just can't think of a way to implement this!
How can I implement such a method?
Note: don't tell me this is not possible. In the Instagram app, I can see different images that have different heights fit perfectly in a table view cell. And those images are similar to my web views. They both need time to load.
EDIT:
Let me show some of my attempts at this:
var results: [Entry] = []
var cellHeights: [CGFloat] = []
var webViews: [UIWebView] = []
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("resultCell")
let webView = cell!.contentView.viewWithTag(1) as! UIWebView
webView.loadHTMLString(results[indexPath.row].htmlDescriptionForSearchMode(.TitleOnly), baseURL: nil)
webView.delegate = self
webView.scrollView.scrollEnabled = false
webViews.append(webView)
cellHeights.append(400)
webView.stringByEvaluatingJavaScriptFromString("highlightSearch(\"day\")")
return cell!
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return indexPath.row < cellHeights.count ? cellHeights[indexPath.row] : 400
}
func webViewDidFinishLoad(webView: UIWebView) {
let height = CGFloat(webView.stringByEvaluatingJavaScriptFromString("document.height")!.toFloat()!)
webView.frame = CGRect(origin: webView.frame.origin, size: CGSizeMake(webView.frame.width, height))
print(height)
if let index = webViews.indexesOf(webView).first {
cellHeights[index] = height
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .None)
tableView.endUpdates()
}
}
results
is the stuff that I want to show in the web views. cellHeights
is used to store the height of each cell. I put all the web views into the webViews
array so I can call indexOf
in webViewDidFinishLoad
to identify which web view is loaded.
EDIT:
So I wrote this code in my table view controller with reference to Andre's answer:
class SearchResultsController: UITableViewController, UIWebViewDelegate {
var entries: [Entry] = []
lazy var results: [Result] = {
return self.entries.map { Result(entry: $0) }
}()
var cellHeights: [CGFloat] = []
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let result = results[indexPath.section]
var cell = result.cell
if cell == nil {
print("cellForRow called")
cell = tableView.dequeueReusableCellWithIdentifier("resultCell") as! ResultCell
cell.webView.delegate = self
print(cell == nil)
print("loading \(result.entry.title)...")
cell.webView.loadHTMLString(result.entry.htmlDescriptionForSearchMode(.TitleOnly), baseURL: nil)
result.cell = cell
}
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return indexPath.row < cellHeights.count ? cellHeights[indexPath.row] : 400
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 169
tableView.rowHeight = UITableViewAutomaticDimension
tableView.tableFooterView = UIView()
}
func webViewDidFinishLoad(webView: UIWebView) {
print("didFinishLoad called")
if webView.loading {
return
}
guard let cell = webView.superview?.superview as? ResultCell else {
print("could not get cell")
return
}
guard let index = results.map({$0.cell}).indexOf(cell) else {
print("could not get index")
return
}
let result = results[index]
print("finished loading \(result.entry.title)...")
guard let heightString = webView.stringByEvaluatingJavaScriptFromString("document.height") else {
print("could not get heightString")
return
}
guard let contentHeight = Float(heightString) else {
print("could not convert heightString")
return
}
cell.webViewHeightConstraint.constant = CGFloat(contentHeight)
tableView.beginUpdates()
tableView.endUpdates()
}
}
class ResultCell: UITableViewCell {
@IBOutlet weak var webView: UIWebView!
@IBOutlet weak var webViewHeightConstraint: NSLayoutConstraint!
}
class Result {
let entry: Entry
var contentHeight: Float?
var cell: ResultCell!
init(entry: Entry) {
self.entry = entry
}
}