ios - NSLayoutManager:如何在只有可渲染字形的地方填充背景颜色
问题描述
默认布局管理器填充没有文本(最后一行除外)的背景颜色(通过 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
}
但是,这不能处理属性字符串中由范围指定的多种颜色的可能性。我怎样才能做到这一点?我看过fillBackgroundRectArray
,收效甚微。
解决方案
我怎样才能做到这一点?
以下是我如何达到您的目标,以在著名的内部以不同颜色突出显示“坐”Lorem ipsum...
一词,该词大到足以在多行上进行测试。
此答案中提供了支持以下代码(Swift 5.1,iOS 13)的所有基础知识,为了清楚起见,不会在此处复制⟹它们导致了以下结果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,它显示了在文本中找到的“sit”lineFragment
的每个彩色背景都有问题⟹和彩色背景矩形之间存在偏移。
我去看了fillBackgroundRectArray
你提到的方法,Apple 说它“用颜色填充背景矩形”和“是 ' 使用的原始方法drawBackground
”:似乎在这里纠正布局问题是完美的。
MyLayoutManager.swift
override func fillBackgroundRectArray(_ rectArray: UnsafePointer<CGRect>,
count rectCount: Int,
forCharacterRange charRange: NSRange,
color: UIColor) {
self.enumerateLineFragments(forGlyphRange: charRange) { (rect, usedRect, textContainer, glyphRange, stop) in
var newRect = rectArray[0]
newRect.origin.y = usedRect.origin.y + (usedRect.size.height / 4.0)
newRect.size.height = usedRect.size.height / 2.0
let currentContext = UIGraphicsGetCurrentContext()
currentContext?.saveGState()
currentContext?.setFillColor(color.cgColor)
currentContext?.fill(newRect)
currentContext?.restoreGState()
}
}
需要深入探索参数调整以获得通用公式,但对于示例,它可以正常工作。
最后,我们得到结果3,一旦调整了正则表达式的条件,就可以在属性字符串中使用范围指定的多种颜色。
推荐阅读
- mysql - 混淆的MySQL答案加起来不一样
- swift - CMLogItem 时间戳:为什么这么复杂?
- javascript - 使用动态文本框进行计算
- javascript - javascript 从 Date 对象获取时间信息
- sed - 添加 | 使用 sed
- dart - Dart 中的函数字段返回类型
- libraw - Libraw 不使用相机提供的白平衡
- python - python中的sql INSERT(postgres,游标,执行)
- database - 从事微软访问项目。当我尝试打开表单时收到错误“查询必须有一个目标字段”
- python - 在每个 n 位置将一个列表的值插入另一个列表