Last active
February 4, 2018 10:42
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.