You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义NSLayoutManager中绘制UIImage无显示,求解决方案

解决自定义NSLayoutManager绘制模糊矩形不显示的问题

我来帮你排查并解决这个问题,你的代码里有几个关键问题导致矩形没显示出来,咱们一步步梳理:

核心问题分析

  1. 上下文混用冲突:你同时用了UIGraphicsBeginImageContext()和UIGraphicsImageRenderer,这两个是不同的绘图上下文管理方式,会互相干扰,导致生成的图像无法正确绘制到目标上下文。
  2. 坐标未适配origin:在drawGlyphs(forGlyphRange:at:)方法中,所有glyph相关的坐标都是相对于origin参数的本地坐标,你直接用blurRect绘制会偏离正确位置。
  3. 缺少父类方法调用:如果不先调用super.drawGlyphs(...),要么你的绘制会被系统的文字绘制覆盖,要么文字根本不会显示,同时也会影响上下文的正确状态。
  4. 上下文状态未保护:直接修改上下文属性可能影响后续系统绘图操作,需要保存和恢复上下文状态。

修正后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 23:29:07