Aod*_*odh 6 nslayoutmanager core-text ios textkit swift
默认布局管理器填充没有文本(最后一行除外)的背景颜色(通过 NSAttributedString .backgroundColor 属性指定)。
我已经通过子类化 NSLayoutManager 并覆盖来实现我想要的效果,func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint)如下所示:
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
}
Run Code Online (Sandbox Code Playgroud)
但是,这不能处理属性字符串中范围指定的多种颜色的可能性。我怎样才能实现这个目标?我看过fillBackgroundRectArray但收效甚微。
或者,您可以完全绕过使用属性,如下所示:
所以首先我定义了这个结构:
struct HighlightBackground {
let range: NSRange
let color: NSColor
}
Run Code Online (Sandbox Code Playgroud)
然后在我的 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)
}
Run Code Online (Sandbox Code Playgroud)
然后从您的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()
}
Run Code Online (Sandbox Code Playgroud)
最后,您将在 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
}
Run Code Online (Sandbox Code Playgroud)
希望这也适用于您的情况。
| 归档时间: |
|
| 查看次数: |
2540 次 |
| 最近记录: |