rollingcodes
rollingcodes

Reputation: 16008

UITextView Draw Invisible/Whitespace Characters

How would I make a UITextView draw invisible characters for tabs, spaces, and newline endings?

I figure it would have to be handled either in the drawRect(CGRect) method or by the layout manager in Text Kit. Any simple and/or intuitive solutions?

I just need to know how to get the CGRect of each whitespace character and which method of override to seamlessly draw a graphic for each whitespace character?

Thanks in advance for any advice/help.
Also, I don't mind if your answer in objective-c although both languages would be preferred.

Upvotes: 5

Views: 2132

Answers (1)

rollingcodes
rollingcodes

Reputation: 16008

I found a better solution than setting the showsInvisibleCharacters property of NSLayoutManager to true, by subclassing NSLayoutManager and overriding the method drawBackgroundForGlyphRange(NSRange, CGPoint), which allows for custom drawings for each whitespace character, for example:

class LayoutManager : NSLayoutManager {

    var text: String? { return textStorage?.string }

    var font: UIFont = UIFont.systemFontOfSize(UIFont.systemFontSize()) { 
        didSet { 
            guard let text = self.text else { return }
            let textRange = NSMakeRange(0, (text as NSString).length)                
            invalidateGlyphsForCharactersInRange(textRange, actualCharacterRange: nil)
            invalidateCharacterAttributesForCharactersInRange(textRange, actualCharacterRange: nil)
        }
    }

    override func drawBackgroundForGlyphRange(glyphsToShow: NSRange, atPoint origin: CGPoint) {

        super.drawBackgroundForGlyphRange(glyphsToShow, atPoint:origin)

        guard let text = self.text else { return }

        enumerateLineFragmentsForGlyphRange(glyphsToShow)
        { (rect: CGRect, usedRect: CGRect, textContainer: NSTextContainer, glyphRange: NSRange, stop: UnsafeMutablePointer<ObjCBool>) -> Void in

            let characterRange = self.characterRangeForGlyphRange(glyphRange, actualGlyphRange: nil)

            // Draw invisible tab space characters

            let line = (self.text as NSString).substringWithRange(characterRange)

            do {

                let expr = try NSRegularExpression(pattern: "\t", options: [])

                expr.enumerateMatchesInString(line, options: [.ReportProgress], range: line.range) 
                { (result: NSTextCheckingResult?, flags: NSMatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) in

                    if let result = result {

                        let range = NSMakeRange(result.range.location + characterRange.location, result.range.length)
                        let characterRect = self.boundingRectForGlyphRange(range, inTextContainer: textContainer)

                        let symbol = "\u{21E5}"
                        let attrs = [NSFontAttributeName : Font]
                        let height = (symbol as NSString).sizeWithAttributes(attrs).height
                        symbol.drawInRect(CGRectOffset(characterRect, 1.0, height * 0.5, withAttributes: attrs)

                    }

                }

            } catch let error as NSError {
                print(error.localizedDescription)
            }

        }

    }

}

Upvotes: 3

Related Questions