Skip to content

Instantly share code, notes, and snippets.

@manmal
Last active February 4, 2018 10:42
Show Gist options
  • Save manmal/c11ef79e306ffab25251ed911b3b0e80 to your computer and use it in GitHub Desktop.
Save manmal/c11ef79e306ffab25251ed911b3b0e80 to your computer and use it in GitHub Desktop.
Cache HTML strings as NSAttributedStrings, with their font face and color changed (keeping traits like bold/italic). Makes use of the awesome locking extensions at https://gist.github.com/einsteinx2/00b9ebd962f3a0f6c9e758f842e4c6f9
import Foundation
import UIKit
// Uses locking from https://gist.github.com/einsteinx2/00b9ebd962f3a0f6c9e758f842e4c6f9
class HTMLAttributedStringCache {
private let lock = NSLock()
private struct Key: Hashable {
let htmlString: String
let font: UIFont
let color: UIColor?
var hashValue: Int {
get {
return htmlString.hashValue
}
}
static func ==(lhs: HTMLAttributedStringCache.Key, rhs: HTMLAttributedStringCache.Key) -> Bool {
return lhs.htmlString == rhs.htmlString && lhs.font == rhs.font && compareOptionals(lhs.color, rhs.color, ==)
}
}
private var attributedStrings: [Key: NSAttributedString] = [:]
@discardableResult
open func attributedString(_ htmlString: String, font: UIFont, color: UIColor? = nil) -> NSAttributedString {
let key = Key(htmlString: htmlString, font: font, color: color)
let existingValue: NSAttributedString? = synchronizedResult(lockable: lock) { attributedStrings[key] }
if let value = existingValue {
return value
} else {
let attributed = htmlString.convertHtml().setFontFace(font: font, color: color)
synchronized(lockable: lock, criticalSection: {
attributedStrings[key] = attributed
})
return attributed
}
}
}
public func compareOptionals<T>(_ lhs: T?, _ rhs: T?, _ compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
switch (lhs, rhs) {
case let (lValue?, rValue?):
return compare(lValue, rValue)
case (nil, nil):
return true
default:
return false
}
}
import Foundation
import UIKit
// https://stackoverflow.com/a/47830442/458603
extension NSMutableAttributedString {
func setFontFace(font: UIFont, color: UIColor? = nil) {
beginEditing()
self.enumerateAttribute(.font, in: NSRange(location: 0, length: self.length)) { (value, range, stop) in
if let f = value as? UIFont, let newFontDescriptor = f.fontDescriptor.withFamily(font.familyName).withSymbolicTraits(f.fontDescriptor.symbolicTraits) {
let newFont = UIFont(descriptor: newFontDescriptor, size: font.pointSize)
removeAttribute(.font, range: range)
addAttribute(.font, value: newFont, range: range)
if let color = color {
removeAttribute(.foregroundColor, range: range)
addAttribute(.foregroundColor, value: color, range: range)
}
}
}
endEditing()
}
}
@manmal
Copy link
Author

manmal commented Feb 4, 2018

I think the locking logic in attributedString(_ htmlString: String, font: UIFont, color: UIColor? = nil) needs improvement - currently 2 threads might execute the actual HTML parsing twice, if they start out concurrently with the same parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment