This implementation is now considered as deprecated and extremely over-engineered. Please, visit the new repository with light-weight implementation: https://github.com/CineDev/ParagraphTextKit. Still, this implementation is fully functional and might be used only if you need to set deploment target below 10.15 or iOS 13.
DescriptedTextStorage is a subclass of NSTextStorage class. It works with whole paragraphs of text and notifies its paragraph delegate if user changes any paragraphs. Delegate receives touched paragraph descriptors.
This behavior is important when any paragraph represents a specific object in your model. So, every single change to the text storage will be reflected in the appropriate object of your model.
As a result, you now get an opportunity to track changes paragraph-by-paragraph and reflect those changes in your model. That will make it easy not only to build a custom business logic with your model, but also to convert that model into a persistant state using, let's say, Core Data.
Important: do not enable lazily attribute fixing (fixesAttributesLazily property) since it will get the whole algorhythm broken.
Just include DescriptedTextStorage.swift, ParagraphDescriptor.swift, CustromAttributes.swift and String+Extensions.swift in your project. Optionally you can add DescriptedTextStorageTests.swift into the Unit Test target of your project to make sure everything works fine.
// setup the system
let textStorage = DescriptedTextStorage()
textStorage.paragraphDelegate = yourDelegateObject
// make sure the deletage is syncronized with the blank text storage state (a blank text storage still has one empty paragraph)
textStorage.paragraphDelegate?.textStorage(textStorage, didAdd: textStorage.paragraphDescriptor(atParagraphIndex: 0))
That's it!
The rest is up to you and depends how you would implement the DescriptedTextStorageDelegate protocol which will update your model synchronously with changes in text storage paragraphs.
Implementing the conformance to DescriptedTextStorageDelegate protocol, use the 'identifier' property of a ParagraphDescriptor object to sync your model with paragraphs of the text storage.
Now it will work. The text storage will automatically track all the paragraph changes and immediately notify its paragraphDelegate about them.
The ParagraphDescriptor struct basically holds the identifier of the paragraph and its range in the text storage.
The DescriptedTextStorage object calculates paragraph ranges in the edited range every time when replaceCharacterInRage method gets called. Diring the attribute fixing it sets as an attribute the identifer of the paragraph descriptor corresponding with the changed paragraph range.
If user sets some attribute in the text storage without changing the text, the DescriptedTextStorage object will make sure that those new attributes have the correct paragraph idenifier before actually applying the attrubutes.