自定义NSLayoutManager中绘制UIImage无显示,求解决方案
解决自定义NSLayoutManager绘制模糊矩形不显示的问题
我来帮你排查并解决这个问题,你的代码里有几个关键问题导致矩形没显示出来,咱们一步步梳理:
核心问题分析
- 上下文混用冲突:你同时用了
UIGraphicsBeginImageContext()和UIGraphicsImageRenderer,这两个是不同的绘图上下文管理方式,会互相干扰,导致生成的图像无法正确绘制到目标上下文。 - 坐标未适配origin:在
drawGlyphs(forGlyphRange:at:)方法中,所有glyph相关的坐标都是相对于origin参数的本地坐标,你直接用blurRect绘制会偏离正确位置。 - 缺少父类方法调用:如果不先调用
super.drawGlyphs(...),要么你的绘制会被系统的文字绘制覆盖,要么文字根本不会显示,同时也会影响上下文的正确状态。 - 上下文状态未保护:直接修改上下文属性可能影响后续系统绘图操作,需要保存和恢复上下文状态。
修正后的完整代码
1. 自定义NSLayoutManager的drawGlyphs方法
override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) { // 先调用父类方法绘制文字,保证文字正常显示,避免绘制顺序问题 super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin) guard let context = UIGraphicsGetCurrentContext() else { return } // --- 这里替换成你实际的blurRect计算逻辑 --- // 示例:获取当前glyphRange对应单词的边界矩形 guard let textContainer = self.textContainer, let textStorage = self.textStorage else { return } let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil) let wordRange = textStorage.string.rangeOfCharacter(from: .whitespacesAndNewlines, range: NSRangeToRange(characterRange)) ?? textStorage.string.endIndex..<textStorage.string.endIndex let wordCharRange = NSRange(wordRange, in: textStorage.string) let wordGlyphRange = self.glyphRange(forCharacterRange: wordCharRange, actualCharacterRange: nil) var blurRect = self.boundingRect(forGlyphRange: wordGlyphRange, in: textContainer) // --- 计算结束 --- // 将本地坐标转换为上下文全局坐标(加上origin偏移) blurRect = blurRect.offsetBy(dx: origin.x, dy: origin.y) // 使用UIGraphicsImageRenderer生成矩形图像(替代旧的Begin/EndImageContext) let renderer = UIGraphicsImageRenderer(size: blurRect.size) let baseImage = renderer.image { rendererCtx in let rect = CGRect(origin: .zero, size: blurRect.size) rendererCtx.cgContext.setFillColor(UIColor.red.cgColor) rendererCtx.cgContext.setStrokeColor(UIColor.black.cgColor) rendererCtx.cgContext.setLineWidth(10) rendererCtx.cgContext.addRect(rect) rendererCtx.cgContext.drawPath(using: .fillStroke) } // 应用高斯模糊 guard let blurredImage = baseImage.applyGaussianBlur(radius: 5) else { return } // 保存上下文状态,避免修改影响后续系统绘图 context.saveGState() // 绘制模糊后的图像到正确位置 blurredImage.draw(in: blurRect) context.restoreGState() } // 辅助方法:NSRange转Range<String.Index> private func NSRangeToRange(_ nsRange: NSRange) -> Range<String.Index> { guard let textStorage = self.textStorage else { return 0..<0 } let start = textStorage.string.index(textStorage.string.startIndex, offsetBy: nsRange.location) let end = textStorage.string.index(start, offsetBy: nsRange.length) return start..<end }
2. 给UIImage添加高斯模糊扩展
extension UIImage { func applyGaussianBlur(radius: CGFloat) -> UIImage? { guard let ciImage = CIImage(image: self) else { return nil } let blurFilter = CIFilter(name: "CIGaussianBlur")! blurFilter.setValue(ciImage, forKey: kCIInputImageKey) blurFilter.setValue(radius, forKey: kCIInputRadiusKey) guard let outputCI = blurFilter.outputImage else { return nil } let ciContext = CIContext(options: [.useSoftwareRenderer: false]) guard let cgImage = ciContext.createCGImage(outputCI, from: outputCI.extent) else { return nil } return UIImage(cgImage: cgImage) } }
关键修改说明
- 移除UIGraphicsBegin/EndImageContext:
UIGraphicsImageRenderer是iOS 10+推荐的绘图API,会自动管理上下文生命周期,避免手动管理的错误。 - 坐标偏移适配:通过
offsetBy(dx:dy:)把blurRect从glyph本地坐标转换到上下文全局坐标,确保绘制在正确位置。 - 先绘制文字:调用
super.drawGlyphs(...)保证文字先绘制,你的模糊矩形会叠加在文字上方(如果要在下方可以调整顺序,但通常上方是需求)。 - 上下文状态保护:
saveGState()和restoreGState()确保你的绘图设置(比如颜色、线宽)不会影响系统后续的文字或其他元素绘制。
这样调整后,红色模糊矩形应该就能正常显示在目标单词上方了。
内容的提问来源于stack exchange,提问作者user1007522
相关产品推荐
相关产品推荐

