Thom Morgan Thom Morgan - 7 months ago 105
Swift Question

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.

Answer

I found a great solution by modifying the NSLayoutManager class

class LayoutManager : NSLayoutManager {

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

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

        super.drawBackgroundForGlyphRange(glyphsToShow, atPoint:origin)

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

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

            // Draw invisible characters

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

            do {

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

                exr.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)

                        ">".drawInRect(CGRectOffset(characterRect, 0.0, 8.0 /* 1/2 font line height */, withAttributes: [])

                    }

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

            }

        }

    }

}