I have a model that is a group of NSMutableAttributedString
s. Well currently it's a wrapper of a MyString
class. Each string manages it's own attributes/state.
typealias NSAttrStringAttr = [NSAttributedString.Key: Any]
typealias MyStringAttribute = (NSAttrStringAttr, NSRange)
class MyString: ObservableObject {
var attributedString: NSMutableAttributedString
@Published var attributes: [MyStringAttribute] = []
init(string: String) {
self.attributedString = NSMutableAttributedString(string: string)
}
}
The main reason for this is so that I can expose changes in the NSAttributedString
s attributes, as follows:
extension MyString {
func updateAttributes() {
self.attributes = []
let range = NSMakeRange(0, self.attributedString.length - 1)
attributedString.enumerateAttributes(in: range, using: {(attrs, range, _) in
self.attributes.append((attrs, range))
})
}
func addAttribute(_ name: NSAttributedString.Key, value: Any, range: NSRange) {
attributedString.addAttribute(name, value: value, range: range)
updateAttributes()
}
func addAttributes(_ attrs: [NSAttributedString.Key : Any] = [:], range: NSRange) {
attributedString.addAttributes(attrs, range: range)
updateAttributes()
}
}
class MyStringGroup {
private var: strings: [MyString] = []
var _attributedString: NSMutableAttributedString
var attributedString {
get {
if let str = _attributedString {
return str
} else {
var newAString = NSMutableAttributedString(string: "")
strings.forEach { self.newAString.append($0.attributedString) }
_attributedString = newAString
}
}
}
var cancellable = Set<AnyCancellable>()
func append(_ string: MyString) {
string.$attributes.sink(receiveValue: { _ in
// Handle rewriting portion of NSAttributedString based on substring update
}).store(in: &cancellable)
strings.append(string
}
func someStringUpdate(..., range: NSRange) {
let str = strings.findRightString(range: range)
str.someStringUpdate(..., range: offsetRangeForString(str, range: range))
}
}
The main reason for this is that strings are linked with strings outside of the text, and they update certain attributes based on user action, responses from the server, etc. The reason for wrapping NSAttributedString
is so that the logic for managing string attributes, and their connection to the other text can be managed separately of the entire text, and MyStringGroup
needs to know if a MyString
instance has updated its attributes so that it can re-render. I have been trying to use the reactive pattern of Combine
, but can't find anyway to natively listen to attribute
changes in NSAttributedString
, as the only way to access attributes is via function attributes(at:effectiveRange:)
and enumerateAttributes(in:using:)
, so currently I'm stuck with wrapping. Is there a better way to propagates changes in the substring/sentence NSAttributedString
without having to wrap it and rewrite all it's methods?