默认的布局管理器填充没有文本(最后一行除外)的背景颜色(通过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
}
但是,这不能解决在属性字符串中具有由range指定的多种颜色的可能性。我该如何实现?我看着fillBackgroundRectArray
收效甚微。
答案 0 :(得分:0)
或者,您可以根本不使用属性,就像这样:
所以首先我定义了这个结构:
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
}
希望这对您也适用。
答案 1 :(得分:0)
我该如何实现?
在这里,我达到您的目标的方式是,在著名的Lorem ipsum...
内以不同的颜色突出显示“ sit” 术语,该术语足够多行测试。
this answer中提供了支持以下代码(Swift 5.1,iOS 13)的所有基础知识,出于清晰原因不会在此处复制⟹它们导致了结果<此后strong> 1 。
在您的情况下,您想突出显示字符串的某些特定部分,这意味着由于这些元素的内容,这些元素应具有专用的键属性⟹在我看来,textStorage
才可以解决。
MyTextStorage.swift
// Sent when a modification appears via the 'replaceCharacters' method.
override func processEditing() {
var regEx: NSRegularExpression
do {
regEx = try NSRegularExpression.init(pattern: " sit ", options: .caseInsensitive)
let stringLength = backingStorage.string.distance(from: backingStorage.string.startIndex,
to: backingStorage.string.endIndex)
regEx.enumerateMatches(in: backingStorage.string,
options: .reportCompletion,
range: NSRange(location: 1, length: stringLength-1)) { (result, flags, stop) in
guard let result = result else { return }
self.setAttributes([NSAttributedString.Key.foregroundColor : UIColor.black, //To be seen above every colors.
NSAttributedString.Key.backgroundColor : UIColor.random()],
range: result.range)
}
} catch let error as NSError {
print(error.description)
}
super.processEditing()
}
}
//A random color is provided for each " sit " element to highlight the possible different colors in a string.
extension UIColor {
static func random () -> UIColor {
return UIColor(red: CGFloat.random(in: 0...1),
green: CGFloat.random(in: 0...1),
blue: CGFloat.random(in: 0...1),
alpha: 1.0)
}
}
如果从此处构建并运行,则会得到结果 2 ,该结果表明在文本found中出现的每个“坐” 彩色背景都存在问题,⟹会有偏移量lineFragment
和彩色背景矩形之间。 ?
我去看看您提到的fillBackgroundRectArray
方法,以及哪个苹果公司声明它“用颜色填充背景矩形” ,而'是{ {1}}':这里似乎很适合纠正布局问题。 ?
MyLayoutManager.swift
drawBackground
需要深入研究参数调整以具有通用公式,但对于本示例而言,它可以正常工作。
最后,我们得到的结果 3 使得一旦调整了正则表达式的条件,就可以使属性字符串中的范围指定多种颜色。 ???