В качестве альтернативы вы можете вообще обойтись без использования атрибутов, например:
Итак, сначала я определил эту структуру:
struct HighlightBackground {
let range: NSRange
let color: NSColor
}
Затем в моем подклассе NSTextView:
var highlightBackgrounds = [HighlightBackground]()
override func setSelectedRanges(_ ranges: [NSValue], affinity: NSSelectionAffinity, stillSelecting stillSelectingFlag: Bool) {
if stillSelectingFlag == false {
return
}
// remove old ranges first
highlightBackgrounds = highlightBackgrounds.filter { $0.color != .green }
for value in ranges {
let range = value.rangeValue
highlightBackgrounds.append(HighlightBackground(range: range, color: .green))
}
super.setSelectedRanges(ranges, affinity: affinity, stillSelecting: stillSelectingFlag)
}
И затем вызовите это из вашего draw(_ rect: NSRect)
метода:
func showBackgrounds() {
guard
let context = NSGraphicsContext.current?.cgContext,
let lm = self.layoutManager
else { return }
context.saveGState()
// context.translateBy(x: origin.x, y: origin.y)
for bg in highlightBackgrounds {
bg.color.setFill()
let glRange = lm.glyphRange(forCharacterRange: bg.range, actualCharacterRange: nil)
for rect in lm.rectsForGlyphRange(glRange) {
let path = NSBezierPath(roundedRect: rect, xRadius: selectedTextCornerRadius, yRadius: selectedTextCornerRadius)
path.fill()
}
}
context.restoreGState()
}
Наконец, вам понадобится это в вашем подклассе NSLayoutManager, хотя вы, вероятно, также можете поместить его в подкласс NSTextView:
func rectsForGlyphRange(_ glyphsToShow: NSRange) -> [NSRect] {
var rects = [NSRect]()
guard
let tc = textContainer(forGlyphAt: glyphsToShow.location, effectiveRange: nil)
else { return rects }
enumerateLineFragments(forGlyphRange: glyphsToShow) { _, _, _, effectiveRange, _ in
let rect = self.boundingRect(forGlyphRange: NSIntersectionRange(glyphsToShow, effectiveRange), in: tc)
rects.append(rect)
}
return rects
}
Надеюсь, это работает и в вашем случае.