ios - NSLayoutManager:如何在只有可渲染字形的地方填充背景颜色

标签 ios swift core-text textkit nslayoutmanager

默认布局管理器在没有文本(最后一行除外)的地方填充背景颜色(通过 NSAttributedString .backgroundColor 属性指定)。

enter image description here

我已经通过子类化 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
}

enter image description here

但是,这不处理属性字符串中范围指定多种颜色的可能性。我怎样才能做到这一点?我看过 fillBackgroundRectArray 但收效甚微。

最佳答案

How might I achieve this?

这是我如何实现您的目标,即在著名的 Lorem ipsum... 中以不同颜色突出显示 "sit " 项,它足够大,可以在多个条件下进行测试行。

支持以下代码的所有基础(Swift 5.1,iOS 13)this answer中提供出于清楚的原因,不会在此处复制 ⟹ 它们导致了此后的结果 1

enter image description here

在您的情况下,您想要突出显示字符串的某些特定部分,这意味着这些元素应该具有专用的键属性,因为它们的内容⟹ 在我看来,这取决于 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,一旦调整了正则表达式的条件,就可以在属性字符串中通过范围指定多种颜色。 🎉🥳🎊

关于ios - NSLayoutManager:如何在只有可渲染字形的地方填充背景颜色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53263409/

相关文章:

html - 如何根据浏览器尺寸缩放填充

ios - Swift 错误 - 调用 SimpleAuth 时的额外参数 'option'

ios - 在 Storyboard 中查看数组 MxN 的方法 - Swift 3- Xcode 8.2.1

ios - 如何将数据传递给模型类并从模型类中获取数据 - swift

ios - 附加文本时,由 NSTextStorage 支持的 UITextView 未更新

ios - 在 TextView 中显示自定义文本

ios - 扩展中的声明无法覆盖 Swift 4 中的错误

iPhone 5 的弱光增强模式

ios - 在 xcode 中检测 iOS 版本 - 程序中出现意外的 '@'

ios - 来自 CoreText 的 attributedString 的 LineCount 是错误的