The default layout manager fills in the background color (specified via NSAttributedString .backgroundColor attribute) where there's no text (except for the last line).
I've managed to achieve the effect I want by sublclassing NSLayoutManager and overriding func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint)
as follows:
override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
guard let textContainer = textContainers.first, let textStorage = textStorage else { fatalError() }
// This just takes the color of the first character assuming the entire container has the same background color.
// To support ranges of different colours, you'll need to draw each glyph separately, querying the attributed string for the
// background color attribute for the range of each character.
guard textStorage.length > 0, let backgroundColor = textStorage.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? UIColor else { return }
var lineRects = [CGRect]()
// create an array of line rects to be drawn.
enumerateLineFragments(forGlyphRange: glyphsToShow) { (_, usedRect, _, range, _) in
var usedRect = usedRect
let locationOfLastGlyphInLine = NSMaxRange(range)-1
// Remove the space at the end of each line (except last).
if self.isThereAWhitespace(at: locationOfLastGlyphInLine) {
let lastGlyphInLineWidth = self.boundingRect(forGlyphRange: NSRange(location: locationOfLastGlyphInLine, length: 1), in: textContainer).width
usedRect.size.width -= lastGlyphInLineWidth
}
lineRects.append(usedRect)
}
lineRects = adjustRectsToContainerHeight(rects: lineRects, containerHeight: textContainer.size.height)
for (lineNumber, lineRect) in lineRects.enumerated() {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
context.setFillColor(backgroundColor.cgColor)
context.fill(lineRect)
context.restoreGState()
}
}
private func isThereAWhitespace(at location: Int) -> Bool {
return propertyForGlyph(at: location) == NSLayoutManager.GlyphProperty.elastic
}
However, this doesn't handle the possibility of having multiple colors specified by range in the attributed string. How might I achieve this? I've looked at fillBackgroundRectArray
with little success.