In this episode Sam Soffes joins us again to show how to implement some rudimentary syntax highlighting of text while you type using TextKit. This builds on the concepts we learned in episode 207, so start there first!
About the Author Sam Soffes is an experienced iOS developer and regular contributor to NSScreencast, with extensive experience in TextKit. You find Sam on Twitter. Episode Links Source Code Custom Text Storage Class We can leverage the processEditing method to do our work: class SyntaxTextStorage: BaseTextStorage { override func processEditing() { let text = string as NSString setAttributes([ NSFontAttributeName: UIFont.systemFontOfSize(18) ], range: NSRange(location: 0, length: length)) text.enumerateSubstringsInRange(NSRange(location: 0, length: length), options: .ByWords) { [weak self] string, range, _, _ in guard let string = string else { return } if string.lowercaseString == "red" { self?.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: range) } else if string.lowercaseString == "bold" { self?.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(18), range: range) } } super.processEditing() } } Note that this is extremely naive, as we're re-processing the entire string on every keystroke. It would be more efficient to see what changed and only recompute those attributes, however that is fairly complicated. The base class we're using is a simple one that just leverages NSAttributedString as a backing storage: class BaseTextStorage: NSTextStorage { // MARK: - Properties private let storage = NSMutableAttributedString() // MARK: - NSTextStorage override var string: String { return storage.string } override func attributesAtIndex(location: Int, effectiveRange: NSRangePointer) -> [String : AnyObject] { return storage.attributesAtIndex(location, effectiveRange: effectiveRange) } override func replaceCharactersInRange(range: NSRange, withString string: String) { let beforeLength = length storage.replaceCharactersInRange(range, withString: string) edited(.EditedCharacters, range: range, changeInLength: length - beforeLength) } override func setAttributes(attributes: [String : AnyObject]?, range: NSRange) { storage.setAttributes(attributes, range: range) edited(.EditedAttributes, range: range, changeInLength: 0) } }