Skip to content

Instantly share code, notes, and snippets.

@daltonclaybrook
Created January 15, 2022 19:13
Show Gist options
  • Save daltonclaybrook/42fe6c4f18f4882c9725c95204e8db19 to your computer and use it in GitHub Desktop.
Save daltonclaybrook/42fe6c4f18f4882c9725c95204e8db19 to your computer and use it in GitHub Desktop.
An Equatable, value-semantic container for NSAttributedString attributes
import Foundation
/// An Equatable container for storing and accessing just the attributes of an NSAttributedString without
/// caring about the string itself.
struct TextAttributes: Equatable {
private var storage = NSMutableAttributedString(string: "")
mutating func setAttributes(_ attributes: [NSAttributedString.Key: Any], range: NSRange) {
preserveUniqueReferenceToStorageIfNecessary()
extendStringRangeIfNecessary(range: range)
storage.setAttributes(attributes, range: range)
}
mutating func addAttributes(_ attributes: [NSAttributedString.Key: Any], range: NSRange) {
preserveUniqueReferenceToStorageIfNecessary()
extendStringRangeIfNecessary(range: range)
storage.addAttributes(attributes, range: range)
}
func allAttributes() -> [(range: NSRange, attributes: [NSAttributedString.Key: Any])] {
var result: [(NSRange, [NSAttributedString.Key: Any])] = []
storage.enumerateAttributes(in: NSRange(location: 0, length: storage.length), options: []) { attributes, range, stop in
result.append((range, attributes))
}
return result
}
func attributes(at location: Int) -> [NSAttributedString.Key: Any]? {
guard location < storage.length else { return nil }
return storage.attributes(at: location, effectiveRange: nil)
}
// MARK: - Private helpers
private mutating func preserveUniqueReferenceToStorageIfNecessary() {
if isKnownUniquelyReferenced(&storage) == false {
// preserve value semantics
storage = storage.mutableCopy() as! NSMutableAttributedString
}
}
private mutating func extendStringRangeIfNecessary(range: NSRange) {
if range.upperBound > storage.length {
let extraLength = range.upperBound - storage.length
storage.append(NSAttributedString(string: String(repeating: " ", count: extraLength)))
}
}
}
extension NSMutableAttributedString {
func apply(attributes: TextAttributes) {
for next in attributes.allAttributes() {
guard next.range.upperBound <= self.length else { continue }
self.setAttributes(next.attributes, range: next.range)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment