如何在NSTextView中正确为指定字符设置圆角矩形背景色?
实现NSTextView中带圆角背景的Tag文本
嘿,我来帮你搞定这个给特定字符加圆角矩形背景的需求!你已经找对了方向——重写NSLayoutManager的fillBackgroundRectArray方法是实现自定义背景绘制的关键,下面是完整的实现方案:
1. 自定义NSLayoutManager子类
我们需要在这个子类里识别带tag属性的文本范围,然后替换默认的矩形背景为圆角背景:
class TagLayoutManager: NSLayoutManager { override func fillBackgroundRectArray(_ rectArray: UnsafePointer<NSRect>, count rectCount: Int, forCharacterRange charRange: NSRange, color: NSColor) { guard let textStorage = self.textStorage else { // 如果没有textStorage, fallback到父类的默认实现 super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: charRange, color: color) return } var currentProcessingRange = charRange // 遍历当前字符范围内的所有属性块 while currentProcessingRange.length > 0 { var effectiveAttributeRange = NSRange() let attributes = textStorage.attributes(at: currentProcessingRange.location, effectiveRange: &effectiveAttributeRange) // 只处理当前字符范围内的属性块交集 let targetRange = NSIntersectionRange(currentProcessingRange, effectiveAttributeRange) if attributes["tag"] != nil { // 对带tag属性的文本,绘制圆角背景 let cornerRadius = rectArray[0].height * 0.4 // 圆角半径设为文本高度的40%,比例更协调 for index in 0..<rectCount { let rect = rectArray[index] let roundedPath = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius) color.setFill() roundedPath.fill() } } else { // 非tag文本,调用父类方法绘制默认背景 super.fillBackgroundRectArray(rectArray, count: rectCount, forCharacterRange: targetRange, color: color) } // 移动到下一个属性块继续处理 currentProcessingRange.location = NSMaxRange(effectiveAttributeRange) currentProcessingRange.length = charRange.length - (currentProcessingRange.location - charRange.location) } } }
2. 配置NSTextView使用自定义LayoutManager
创建NSTextView时,需要把自定义的TagLayoutManager关联到它的文本系统中:
// 构建文本系统三组件:TextStorage -> LayoutManager -> TextContainer let textStorage = NSTextStorage() let tagLayoutManager = TagLayoutManager() let textContainer = NSTextContainer(size: NSSize(width: 350, height: CGFloat.greatestFiniteMagnitude)) tagLayoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(tagLayoutManager) // 创建NSTextView let textView = NSTextView(frame: NSRect(x: 20, y: 20, width: 350, height: 250), textContainer: textContainer) textView.isEditable = false // 根据你的需求设置是否可编辑 textView.drawsBackground = true
3. 添加带Tag属性的文本
给需要标记的文本添加tag属性和背景色,这样自定义LayoutManager就会识别并绘制圆角:
let mutableAttributedString = NSMutableAttributedString(string: "这是一段普通文本,[重要标签1] 这又是普通文本,[重要标签2]") // 给"[重要标签1]"添加tag属性 let tagRange1 = (mutableAttributedString.string as NSString).range(of: "[重要标签1]") mutableAttributedString.addAttribute("tag", value: true, range: tagRange1) mutableAttributedString.addAttribute(NSBackgroundColorAttributeName, value: NSColor.systemOrange.withAlphaComponent(0.3), range: tagRange1) // 给"[重要标签2]"添加tag属性 let tagRange2 = (mutableAttributedString.string as NSString).range(of: "[重要标签2]") mutableAttributedString.addAttribute("tag", value: true, range: tagRange2) mutableAttributedString.addAttribute(NSBackgroundColorAttributeName, value: NSColor.systemBlue.withAlphaComponent(0.3), range: tagRange2) // 设置到textView的textStorage中 textStorage.setAttributedString(mutableAttributedString)
关键注意点
- 圆角自适应:我们用文本高度的40%作为圆角半径,这样不同字号的tag文本都能保持协调的圆角比例。
- 换行处理:如果tag文本换行,
rectArray会包含多个换行后的rect,每个都会被绘制圆角,保证换行后的每一段tag文本都有完整的圆角背景。 - 性能优化:通过遍历属性块而非单个字符,避免不必要的重复绘制,保证文本较多时的性能。
内容的提问来源于stack exchange,提问作者Henry
相关产品推荐
相关产品推荐

