Categories
grand-central-dispatch ios nsattributedstring swift uitableview

Very slow HTML rendering in NSAttributedString

I have UITableView with dynamic sizing cells that displays list of comments in HTML format and I faced with the problem that NSAttributedString renders HTML content extremely slow!

Here is snapshot from profiler.

enter image description here

I tried to put the NSAttributedString initialization to separate thread, but still slow and user sees empty cells while HTML is being rendered and finally when it finished rendering cell is not layout properly.

    dispatch_async(GlobalQueue, {
let html = NSAttributedString(
data: self.comment.htmlBody.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: false)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil,
error: nil)
dispatch_async(MainQueue, {
self.commentLabel.attributedText = html
self.commentLabel.font = UIFont(name: "HelveticaNeue-Light", size: 14.0)!
if let writer = self.comment.author {
self.authorLabel.text = writer.name
}
self.layoutIfNeeded()
})
})

Looks following
enter image description here

Please advice how to speed up rendering and fix cell layout.

Thanks!

UPDATE:

Solved with cell delegate and flag indicating that attributed string is initialized. Maybe would help somebody:

// TicketCell    
private var isContentInitialized = false
private var commentAttributedString:NSAttributedString?
var delegate: TicketCommentCellDelegate?
var indexPath: NSIndexPath!
var comment: TicketComment! {
willSet {
if newValue != self.comment {
self.isContentInitialized = false
}
}
didSet{
self.configure()
}
}
...
private func configure() {
if isContentInitialized {
// here might be activity indicator stop
...
if let writer = self.comment.author {
self.authorLabel.text = writer.name
}
}
else {
// here might be activity indicator start
dispatch_async(GlobalQueue, {
self.commentAttributedString = NSAttributedString(
data: self.comment.htmlBody.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: false)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil,
error: nil)
self.isContentInitialized = true
// here might be spinner stop
dispatch_async(MainQueue, {
self.delegate?.ticketCommentCellDidRenderCommentHTML(self)
})
})
}
}
...
protocol TicketCommentCellDelegate {
func ticketCommentCellDidRenderCommentHTML(cell: TicketCommentCell)
}
// TableViewDataSource
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(kTicketCommentCellIdentifier, forIndexPath: indexPath) as! TicketCommentCell
cell.indexPath = indexPath
cell.delegate = self
cell.comment = self.rows[indexPath.section][indexPath.row]
return cell
}
// MARK: - TicketCommentCellDelegate
func ticketCommentCellDidRenderCommentHTML(cell: TicketCommentCell) {
self.tableView.reloadRowsAtIndexPaths([cell.indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
// MARK: UITableViewDelegate
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = self.commentCell
cell.comment = self.rows[indexPath.section][indexPath.row]
cell.setNeedsDisplay()
cell.setNeedsLayout()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
return height
}